From f04ef79f619983bfb4a7c9908bdae62e0d0d5ba7 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Thu, 15 May 2025 10:54:23 +0200 Subject: [PATCH 001/311] build: bump version to 0.20.0-dev --- meson.build | 2 +- tinywl/Makefile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/meson.build b/meson.build index 016e59842..fc7c179a6 100644 --- a/meson.build +++ b/meson.build @@ -1,7 +1,7 @@ project( 'wlroots', 'c', - version: '0.19.0', + version: '0.20.0-dev', license: 'MIT', meson_version: '>=1.3', default_options: [ diff --git a/tinywl/Makefile b/tinywl/Makefile index a5cedfcc8..70dc671ca 100644 --- a/tinywl/Makefile +++ b/tinywl/Makefile @@ -2,7 +2,7 @@ PKG_CONFIG?=pkg-config WAYLAND_PROTOCOLS!=$(PKG_CONFIG) --variable=pkgdatadir wayland-protocols WAYLAND_SCANNER!=$(PKG_CONFIG) --variable=wayland_scanner wayland-scanner -PKGS="wlroots-0.19" wayland-server xkbcommon +PKGS="wlroots-0.20" wayland-server xkbcommon CFLAGS_PKG_CONFIG!=$(PKG_CONFIG) --cflags $(PKGS) CFLAGS+=$(CFLAGS_PKG_CONFIG) LIBS!=$(PKG_CONFIG) --libs $(PKGS) From c9f0dbc159f0d5408f28477c62e6ee8038726f1d Mon Sep 17 00:00:00 2001 From: David Turner Date: Thu, 24 Apr 2025 14:12:43 +0100 Subject: [PATCH 002/311] Change all timespec pointers in events to owned Follow-up from !4803. Make things consistent by making all `struct timespec`s in events owned. Reduces the need for thinking about ownership/lifetimes. --- include/wlr/types/wlr_output.h | 4 ++-- types/ext_image_capture_source_v1/output.c | 4 ++-- types/output/output.c | 4 ++-- types/wlr_cursor.c | 2 +- types/wlr_export_dmabuf_v1.c | 4 ++-- types/wlr_screencopy_v1.c | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/include/wlr/types/wlr_output.h b/include/wlr/types/wlr_output.h index 9c08a2c34..31b757062 100644 --- a/include/wlr/types/wlr_output.h +++ b/include/wlr/types/wlr_output.h @@ -245,13 +245,13 @@ struct wlr_output_event_damage { struct wlr_output_event_precommit { struct wlr_output *output; - struct timespec *when; + struct timespec when; const struct wlr_output_state *state; }; struct wlr_output_event_commit { struct wlr_output *output; - struct timespec *when; + struct timespec when; const struct wlr_output_state *state; }; diff --git a/types/ext_image_capture_source_v1/output.c b/types/ext_image_capture_source_v1/output.c index e5ef78f93..a2726c759 100644 --- a/types/ext_image_capture_source_v1/output.c +++ b/types/ext_image_capture_source_v1/output.c @@ -40,7 +40,7 @@ struct wlr_ext_output_image_capture_source_v1 { struct wlr_ext_output_image_capture_source_v1_frame_event { struct wlr_ext_image_capture_source_v1_frame_event base; struct wlr_buffer *buffer; - struct timespec *when; + struct timespec when; }; static void output_source_start(struct wlr_ext_image_capture_source_v1 *base, @@ -85,7 +85,7 @@ static void output_source_copy_frame(struct wlr_ext_image_capture_source_v1 *bas if (wlr_ext_image_copy_capture_frame_v1_copy_buffer(frame, event->buffer, source->output->renderer)) { wlr_ext_image_copy_capture_frame_v1_ready(frame, - source->output->transform, event->when); + source->output->transform, &event->when); } } diff --git a/types/output/output.c b/types/output/output.c index a352a5e59..346a3aa40 100644 --- a/types/output/output.c +++ b/types/output/output.c @@ -728,7 +728,7 @@ bool output_prepare_commit(struct wlr_output *output, const struct wlr_output_st struct wlr_output_event_precommit pre_event = { .output = output, - .when = &now, + .when = now, .state = state, }; wl_signal_emit_mutable(&output->events.precommit, &pre_event); @@ -750,7 +750,7 @@ void output_apply_commit(struct wlr_output *output, const struct wlr_output_stat clock_gettime(CLOCK_MONOTONIC, &now); struct wlr_output_event_commit event = { .output = output, - .when = &now, + .when = now, .state = state, }; wl_signal_emit_mutable(&output->events.commit, &event); diff --git a/types/wlr_cursor.c b/types/wlr_cursor.c index 44b74e926..7f99fef0c 100644 --- a/types/wlr_cursor.c +++ b/types/wlr_cursor.c @@ -650,7 +650,7 @@ static void output_cursor_output_handle_output_commit( struct wlr_surface *surface = output_cursor->cursor->state->surface; if (surface && output_cursor->output_cursor->visible && (event->state->committed & WLR_OUTPUT_STATE_BUFFER)) { - wlr_surface_send_frame_done(surface, event->when); + wlr_surface_send_frame_done(surface, &event->when); } } diff --git a/types/wlr_export_dmabuf_v1.c b/types/wlr_export_dmabuf_v1.c index ea4a53880..4e629dcfd 100644 --- a/types/wlr_export_dmabuf_v1.c +++ b/types/wlr_export_dmabuf_v1.c @@ -85,11 +85,11 @@ static void frame_output_handle_commit(struct wl_listener *listener, attribs.fd[i], size, attribs.offset[i], attribs.stride[i], i); } - time_t tv_sec = event->when->tv_sec; + time_t tv_sec = event->when.tv_sec; uint32_t tv_sec_hi = (sizeof(tv_sec) > 4) ? tv_sec >> 32 : 0; uint32_t tv_sec_lo = tv_sec & 0xFFFFFFFF; zwlr_export_dmabuf_frame_v1_send_ready(frame->resource, - tv_sec_hi, tv_sec_lo, event->when->tv_nsec); + tv_sec_hi, tv_sec_lo, event->when.tv_nsec); frame_destroy(frame); } diff --git a/types/wlr_screencopy_v1.c b/types/wlr_screencopy_v1.c index c49ed6bdf..58e8dc8ba 100644 --- a/types/wlr_screencopy_v1.c +++ b/types/wlr_screencopy_v1.c @@ -334,7 +334,7 @@ static void frame_handle_output_commit(struct wl_listener *listener, zwlr_screencopy_frame_v1_send_flags(frame->resource, 0); frame_send_damage(frame); - frame_send_ready(frame, event->when); + frame_send_ready(frame, &event->when); frame_destroy(frame); return; From c2327248f8a22b3e66e00a6e8bccb3771834cad9 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sun, 20 Apr 2025 21:46:11 +0200 Subject: [PATCH 003/311] output: don't send make/model - These are legacy wl_output properties [1] - wl_output exposes name and description, which are better defined - It's not clear what make/model/serial are for a virtual output - Clients shouldn't rely on these fields [1]: https://gitlab.freedesktop.org/wayland/wayland/-/blob/8f1795f9115bfb71cf143cea43bcb102b6c5c3ea/protocol/wayland.xml?page=3#L2508 Closes: https://gitlab.freedesktop.org/wlroots/wlroots/-/issues/1623 --- types/output/output.c | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/types/output/output.c b/types/output/output.c index 346a3aa40..55f8a7645 100644 --- a/types/output/output.c +++ b/types/output/output.c @@ -16,20 +16,9 @@ static void send_geometry(struct wl_resource *resource) { struct wlr_output *output = wlr_output_from_resource(resource); - - const char *make = output->make; - if (make == NULL) { - make = "Unknown"; - } - - const char *model = output->model; - if (model == NULL) { - model = "Unknown"; - } - wl_output_send_geometry(resource, 0, 0, output->phys_width, output->phys_height, output->subpixel, - make, model, output->transform); + "Unknown", "Unknown", output->transform); } static void send_current_mode(struct wl_resource *resource) { From 62c86fb9754524e0779066e8f32730f0b85dcb6e Mon Sep 17 00:00:00 2001 From: Martin Rys Date: Tue, 18 Feb 2025 14:36:15 +0100 Subject: [PATCH 004/311] Add support for XKB_LED_NAME_COMPOSE and XKB_LED_NAME_KANA USB HID LEDs Requires xkbcommon 1.8.0 --- include/wlr/types/wlr_keyboard.h | 4 +++- meson.build | 4 ++-- types/wlr_keyboard.c | 2 ++ 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/include/wlr/types/wlr_keyboard.h b/include/wlr/types/wlr_keyboard.h index 23ca006a1..20a310fde 100644 --- a/include/wlr/types/wlr_keyboard.h +++ b/include/wlr/types/wlr_keyboard.h @@ -16,12 +16,14 @@ #include #include -#define WLR_LED_COUNT 3 +#define WLR_LED_COUNT 5 enum wlr_keyboard_led { WLR_LED_NUM_LOCK = 1 << 0, WLR_LED_CAPS_LOCK = 1 << 1, WLR_LED_SCROLL_LOCK = 1 << 2, + WLR_LED_COMPOSE = 1 << 3, + WLR_LED_KANA = 1 << 4, }; #define WLR_MODIFIER_COUNT 8 diff --git a/meson.build b/meson.build index fc7c179a6..8319eff69 100644 --- a/meson.build +++ b/meson.build @@ -105,8 +105,8 @@ drm = dependency('libdrm', 'tests=false', ], ) -xkbcommon = dependency( - 'xkbcommon', +xkbcommon = dependency('xkbcommon', + version: '>=1.8.0', fallback: 'libxkbcommon', default_options: [ 'enable-tools=false', diff --git a/types/wlr_keyboard.c b/types/wlr_keyboard.c index 613b4e841..a24335b3a 100644 --- a/types/wlr_keyboard.c +++ b/types/wlr_keyboard.c @@ -235,6 +235,8 @@ bool wlr_keyboard_set_keymap(struct wlr_keyboard *kb, struct xkb_keymap *keymap) XKB_LED_NAME_NUM, XKB_LED_NAME_CAPS, XKB_LED_NAME_SCROLL, + XKB_LED_NAME_COMPOSE, + XKB_LED_NAME_KANA, }; for (size_t i = 0; i < WLR_LED_COUNT; ++i) { kb->led_indexes[i] = xkb_map_led_get_index(kb->keymap, led_names[i]); From 536100488fc4c64528786801860f96cfa1a55765 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Thu, 10 Apr 2025 14:43:03 +0200 Subject: [PATCH 005/311] text-input-v3: Name new text input event correctly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Helps: https://gitlab.freedesktop.org/wlroots/wlroots/-/issues/3851 Signed-off-by: Guido Günther --- include/wlr/types/wlr_text_input_v3.h | 2 +- types/wlr_text_input_v3.c | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/include/wlr/types/wlr_text_input_v3.h b/include/wlr/types/wlr_text_input_v3.h index 4bc4dbbd9..f9eb1d7ed 100644 --- a/include/wlr/types/wlr_text_input_v3.h +++ b/include/wlr/types/wlr_text_input_v3.h @@ -74,7 +74,7 @@ struct wlr_text_input_manager_v3 { struct wl_list text_inputs; // wlr_text_input_v3.link struct { - struct wl_signal text_input; // struct wlr_text_input_v3 + struct wl_signal new_text_input; // struct wlr_text_input_v3 struct wl_signal destroy; // struct wlr_text_input_manager_v3 } events; diff --git a/types/wlr_text_input_v3.c b/types/wlr_text_input_v3.c index 6c9f07964..1b1ea4df9 100644 --- a/types/wlr_text_input_v3.c +++ b/types/wlr_text_input_v3.c @@ -287,7 +287,7 @@ static void text_input_manager_get_text_input(struct wl_client *client, text_input_manager_from_resource(resource); wl_list_insert(&manager->text_inputs, &text_input->link); - wl_signal_emit_mutable(&manager->events.text_input, text_input); + wl_signal_emit_mutable(&manager->events.new_text_input, text_input); } static const struct zwp_text_input_manager_v3_interface @@ -315,7 +315,7 @@ static void handle_display_destroy(struct wl_listener *listener, void *data) { wl_container_of(listener, manager, display_destroy); wl_signal_emit_mutable(&manager->events.destroy, manager); - assert(wl_list_empty(&manager->events.text_input.listener_list)); + assert(wl_list_empty(&manager->events.new_text_input.listener_list)); assert(wl_list_empty(&manager->events.destroy.listener_list)); wl_list_remove(&manager->display_destroy.link); @@ -332,7 +332,7 @@ struct wlr_text_input_manager_v3 *wlr_text_input_manager_v3_create( wl_list_init(&manager->text_inputs); - wl_signal_init(&manager->events.text_input); + wl_signal_init(&manager->events.new_text_input); wl_signal_init(&manager->events.destroy); manager->global = wl_global_create(display, From 2d5492c73770c9de420527df1098fefabe43d689 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Thu, 10 Apr 2025 14:46:35 +0200 Subject: [PATCH 006/311] text-input-v3: Use `NULL` when emitting signals MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Listeners can use `wl_container_of`. Helps: https://gitlab.freedesktop.org/wlroots/wlroots/-/issues/3851 Signed-off-by: Guido Günther --- types/wlr_text_input_v3.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/types/wlr_text_input_v3.c b/types/wlr_text_input_v3.c index 1b1ea4df9..bbae3a076 100644 --- a/types/wlr_text_input_v3.c +++ b/types/wlr_text_input_v3.c @@ -64,7 +64,7 @@ void wlr_text_input_v3_send_done(struct wlr_text_input_v3 *text_input) { } static void wlr_text_input_destroy(struct wlr_text_input_v3 *text_input) { - wl_signal_emit_mutable(&text_input->events.destroy, text_input); + wl_signal_emit_mutable(&text_input->events.destroy, NULL); assert(wl_list_empty(&text_input->events.enable.listener_list)); assert(wl_list_empty(&text_input->events.commit.listener_list)); @@ -192,12 +192,12 @@ static void text_input_commit(struct wl_client *client, if (!old_enabled && text_input->current_enabled) { text_input->active_features = text_input->current.features; - wl_signal_emit_mutable(&text_input->events.enable, text_input); + wl_signal_emit_mutable(&text_input->events.enable, NULL); } else if (old_enabled && !text_input->current_enabled) { text_input->active_features = 0; - wl_signal_emit_mutable(&text_input->events.disable, text_input); + wl_signal_emit_mutable(&text_input->events.disable, NULL); } else { // including never enabled - wl_signal_emit_mutable(&text_input->events.commit, text_input); + wl_signal_emit_mutable(&text_input->events.commit, NULL); } } @@ -313,7 +313,7 @@ static void text_input_manager_bind(struct wl_client *wl_client, void *data, static void handle_display_destroy(struct wl_listener *listener, void *data) { struct wlr_text_input_manager_v3 *manager = wl_container_of(listener, manager, display_destroy); - wl_signal_emit_mutable(&manager->events.destroy, manager); + wl_signal_emit_mutable(&manager->events.destroy, NULL); assert(wl_list_empty(&manager->events.new_text_input.listener_list)); assert(wl_list_empty(&manager->events.destroy.listener_list)); From 170f7e070603f0ecdadc4527c65bc08b62073e58 Mon Sep 17 00:00:00 2001 From: tokyo4j Date: Tue, 20 May 2025 01:14:16 +0900 Subject: [PATCH 007/311] backend/libinput: don't leak udev_device --- backend/libinput/tablet_pad.c | 1 + backend/libinput/tablet_tool.c | 1 + 2 files changed, 2 insertions(+) diff --git a/backend/libinput/tablet_pad.c b/backend/libinput/tablet_pad.c index 98b3e4f4b..2fbfb6a6c 100644 --- a/backend/libinput/tablet_pad.c +++ b/backend/libinput/tablet_pad.c @@ -104,6 +104,7 @@ void init_device_tablet_pad(struct wlr_libinput_input_device *dev) { struct udev_device *udev = libinput_device_get_udev_device(handle); char **dst = wl_array_add(&wlr_tablet_pad->paths, sizeof(char *)); *dst = strdup(udev_device_get_syspath(udev)); + udev_device_unref(udev); int groups = libinput_device_tablet_pad_get_num_mode_groups(handle); for (int i = 0; i < groups; ++i) { diff --git a/backend/libinput/tablet_tool.c b/backend/libinput/tablet_tool.c index eec697c49..d43c6cd0a 100644 --- a/backend/libinput/tablet_tool.c +++ b/backend/libinput/tablet_tool.c @@ -37,6 +37,7 @@ void init_device_tablet(struct wlr_libinput_input_device *dev) { struct udev_device *udev = libinput_device_get_udev_device(dev->handle); char **dst = wl_array_add(&wlr_tablet->paths, sizeof(char *)); *dst = strdup(udev_device_get_syspath(udev)); + udev_device_unref(udev); wl_list_init(&dev->tablet_tools); } From fae4c5097de0dd03240934a08b5845ac91b3e5e5 Mon Sep 17 00:00:00 2001 From: David Turner Date: Thu, 15 May 2025 10:04:46 +0100 Subject: [PATCH 008/311] xwayland: Remove has_utf8_title field --- include/wlr/xwayland/xwayland.h | 1 - xwayland/xwm.c | 3 --- 2 files changed, 4 deletions(-) diff --git a/include/wlr/xwayland/xwayland.h b/include/wlr/xwayland/xwayland.h index 0eb466d96..92a45a493 100644 --- a/include/wlr/xwayland/xwayland.h +++ b/include/wlr/xwayland/xwayland.h @@ -146,7 +146,6 @@ struct wlr_xwayland_surface { char *role; char *startup_id; pid_t pid; - bool has_utf8_title; struct wl_list children; // wlr_xwayland_surface.parent_link struct wlr_xwayland_surface *parent; diff --git a/xwayland/xwm.c b/xwayland/xwm.c index 8cd1d41ab..d5a57d5e5 100644 --- a/xwayland/xwm.c +++ b/xwayland/xwm.c @@ -774,9 +774,6 @@ static void read_surface_title(struct wlr_xwm *xwm, xsurface->title = NULL; } - // TODO: drop this field - xsurface->has_utf8_title = reply->type == xwm->atoms[UTF8_STRING]; - wl_signal_emit_mutable(&xsurface->events.set_title, NULL); } From aaeffe9769a2056ae1e8672b92add6d14df97413 Mon Sep 17 00:00:00 2001 From: Kirill Primak Date: Wed, 23 Oct 2024 14:39:38 +0300 Subject: [PATCH 009/311] cursor-shape-v1: use generated enum validator --- types/wlr_cursor_shape_v1.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/types/wlr_cursor_shape_v1.c b/types/wlr_cursor_shape_v1.c index 3e657fb6d..83b829bcf 100644 --- a/types/wlr_cursor_shape_v1.c +++ b/types/wlr_cursor_shape_v1.c @@ -46,7 +46,8 @@ static void device_handle_set_shape(struct wl_client *client, struct wl_resource return; } - if (shape == 0 || shape > WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ZOOM_OUT) { + uint32_t version = wl_resource_get_version(device_resource); + if (!wp_cursor_shape_device_v1_shape_is_valid(shape, version)) { wl_resource_post_error(device_resource, WP_CURSOR_SHAPE_DEVICE_V1_ERROR_INVALID_SHAPE, "Invalid shape %"PRIu32, shape); return; From af43d3b9e7017d915dffe7bdeccea3adb8155774 Mon Sep 17 00:00:00 2001 From: Kirill Primak Date: Wed, 23 Oct 2024 14:51:29 +0300 Subject: [PATCH 010/311] cursor-shape-v1: bump to version 2 --- protocol/meson.build | 2 +- types/wlr_cursor_shape_v1.c | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/protocol/meson.build b/protocol/meson.build index 2a04a0377..d092687bc 100644 --- a/protocol/meson.build +++ b/protocol/meson.build @@ -1,5 +1,5 @@ wayland_protos = dependency('wayland-protocols', - version: '>=1.41', + version: '>=1.43', fallback: 'wayland-protocols', default_options: ['tests=false'], ) diff --git a/types/wlr_cursor_shape_v1.c b/types/wlr_cursor_shape_v1.c index 83b829bcf..b8ae07136 100644 --- a/types/wlr_cursor_shape_v1.c +++ b/types/wlr_cursor_shape_v1.c @@ -7,7 +7,7 @@ #include #include "types/wlr_tablet_v2.h" -#define CURSOR_SHAPE_MANAGER_V1_VERSION 1 +#define CURSOR_SHAPE_MANAGER_V1_VERSION 2 struct wlr_cursor_shape_device_v1 { struct wl_resource *resource; @@ -257,6 +257,8 @@ static const char *const shape_names[] = { [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ALL_SCROLL] = "all-scroll", [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ZOOM_IN] = "zoom-in", [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ZOOM_OUT] = "zoom-out", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DND_ASK] = "dnd-ask", + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ALL_RESIZE] = "all-resize", }; const char *wlr_cursor_shape_v1_name(enum wp_cursor_shape_device_v1_shape shape) { From a08acfcee0261ae9b084c217dd70dd52eea2904a Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 29 May 2025 17:38:32 +0800 Subject: [PATCH 011/311] render/pass: Ensure the precision is consistent during comparison --- render/pass.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/render/pass.c b/render/pass.c index 902228273..23bdf96dd 100644 --- a/render/pass.c +++ b/render/pass.c @@ -21,8 +21,8 @@ void wlr_render_pass_add_texture(struct wlr_render_pass *render_pass, if (!wlr_fbox_empty(&options->src_box)) { const struct wlr_fbox *box = &options->src_box; assert(box->x >= 0 && box->y >= 0 && - box->x + box->width <= options->texture->width && - box->y + box->height <= options->texture->height); + (uint32_t)(box->x + box->width) <= options->texture->width && + (uint32_t)(box->y + box->height) <= options->texture->height); } render_pass->impl->add_texture(render_pass, options); From afe427d149e701fb244152e1b44ebaa34e3a4cb0 Mon Sep 17 00:00:00 2001 From: Kirill Primak Date: Thu, 9 May 2024 00:22:15 +0300 Subject: [PATCH 012/311] xdg-shell: add support for v7 --- include/wlr/types/wlr_xdg_shell.h | 10 ++++++++++ types/xdg_shell/wlr_xdg_shell.c | 2 +- types/xdg_shell/wlr_xdg_toplevel.c | 30 ++++++++++++++++++++++++++++-- 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/include/wlr/types/wlr_xdg_shell.h b/include/wlr/types/wlr_xdg_shell.h index 3e9fd2b70..a835bb107 100644 --- a/include/wlr/types/wlr_xdg_shell.h +++ b/include/wlr/types/wlr_xdg_shell.h @@ -141,6 +141,7 @@ enum wlr_xdg_surface_role { struct wlr_xdg_toplevel_state { bool maximized, fullscreen, resizing, activated, suspended; uint32_t tiled; // enum wlr_edges + uint32_t constrained; // enum wlr_edges int32_t width, height; int32_t max_width, max_height; int32_t min_width, min_height; @@ -168,6 +169,7 @@ struct wlr_xdg_toplevel_configure { // The following fields must always be set to reflect the current state bool maximized, fullscreen, resizing, activated, suspended; uint32_t tiled; // enum wlr_edges + uint32_t constrained; // enum wlr_edges int32_t width, height; // Only for WLR_XDG_TOPLEVEL_CONFIGURE_BOUNDS @@ -454,6 +456,14 @@ uint32_t wlr_xdg_toplevel_set_wm_capabilities(struct wlr_xdg_toplevel *toplevel, uint32_t wlr_xdg_toplevel_set_suspended(struct wlr_xdg_toplevel *toplevel, bool suspended); +/** + * Request that this toplevel consider itself constrained and doesn't attempt to + * resize from some edges. `constrained_edges` is a bitfield of enum wlr_edges. + * Returns the associated configure serial. + */ +uint32_t wlr_xdg_toplevel_set_constrained(struct wlr_xdg_toplevel *toplevel, + uint32_t constrained_edges); + /** * Request that this toplevel closes. */ diff --git a/types/xdg_shell/wlr_xdg_shell.c b/types/xdg_shell/wlr_xdg_shell.c index 0cbadc271..2f048ac2c 100644 --- a/types/xdg_shell/wlr_xdg_shell.c +++ b/types/xdg_shell/wlr_xdg_shell.c @@ -2,7 +2,7 @@ #include #include "types/wlr_xdg_shell.h" -#define WM_BASE_VERSION 6 +#define WM_BASE_VERSION 7 static const struct xdg_wm_base_interface xdg_shell_impl; diff --git a/types/xdg_shell/wlr_xdg_toplevel.c b/types/xdg_shell/wlr_xdg_toplevel.c index 950a22b2d..1b33916e7 100644 --- a/types/xdg_shell/wlr_xdg_toplevel.c +++ b/types/xdg_shell/wlr_xdg_toplevel.c @@ -15,6 +15,7 @@ void handle_xdg_toplevel_ack_configure( toplevel->pending.resizing = configure->resizing; toplevel->pending.activated = configure->activated; toplevel->pending.tiled = configure->tiled; + toplevel->pending.constrained = configure->constrained; toplevel->pending.suspended = configure->suspended; toplevel->pending.width = configure->width; @@ -80,7 +81,7 @@ struct wlr_xdg_toplevel_configure *send_xdg_toplevel_configure( states[nstates++] = XDG_TOPLEVEL_STATE_ACTIVATED; } if (configure->tiled && version >= XDG_TOPLEVEL_STATE_TILED_LEFT_SINCE_VERSION) { - const struct { + static const struct { enum wlr_edges edge; enum xdg_toplevel_state state; } tiled[] = { @@ -90,7 +91,7 @@ struct wlr_xdg_toplevel_configure *send_xdg_toplevel_configure( { WLR_EDGE_BOTTOM, XDG_TOPLEVEL_STATE_TILED_BOTTOM }, }; - for (size_t i = 0; i < sizeof(tiled)/sizeof(tiled[0]); ++i) { + for (size_t i = 0; i < sizeof(tiled) / sizeof(tiled[0]); ++i) { if ((configure->tiled & tiled[i].edge) == 0) { continue; } @@ -100,6 +101,24 @@ struct wlr_xdg_toplevel_configure *send_xdg_toplevel_configure( if (configure->suspended && version >= XDG_TOPLEVEL_STATE_SUSPENDED_SINCE_VERSION) { states[nstates++] = XDG_TOPLEVEL_STATE_SUSPENDED; } + if (configure->constrained && version >= XDG_TOPLEVEL_STATE_CONSTRAINED_LEFT_SINCE_VERSION) { + static const struct { + enum wlr_edges edge; + enum xdg_toplevel_state state; + } constrained[] = { + { WLR_EDGE_LEFT, XDG_TOPLEVEL_STATE_CONSTRAINED_LEFT }, + { WLR_EDGE_RIGHT, XDG_TOPLEVEL_STATE_CONSTRAINED_RIGHT }, + { WLR_EDGE_TOP, XDG_TOPLEVEL_STATE_CONSTRAINED_TOP }, + { WLR_EDGE_BOTTOM, XDG_TOPLEVEL_STATE_CONSTRAINED_BOTTOM }, + }; + + for (size_t i = 0; i < sizeof(constrained) / sizeof(constrained[0]); ++i) { + if ((configure->constrained & constrained[i].edge) == 0) { + continue; + } + states[nstates++] = constrained[i].state; + } + } assert(nstates <= sizeof(states) / sizeof(states[0])); int32_t width = configure->width; @@ -638,3 +657,10 @@ uint32_t wlr_xdg_toplevel_set_suspended(struct wlr_xdg_toplevel *toplevel, toplevel->scheduled.suspended = suspended; return wlr_xdg_surface_schedule_configure(toplevel->base); } + +uint32_t wlr_xdg_toplevel_set_constrained(struct wlr_xdg_toplevel *toplevel, uint32_t constrained) { + assert(toplevel->base->client->shell->version >= + XDG_TOPLEVEL_STATE_CONSTRAINED_LEFT_SINCE_VERSION); + toplevel->scheduled.constrained = constrained; + return wlr_xdg_surface_schedule_configure(toplevel->base); +} From 83a5bdf5d546bafdcff57dadd3372b49a8c38708 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Bernon?= Date: Fri, 23 May 2025 18:03:57 +0200 Subject: [PATCH 013/311] xwayland: Create a dummy no_focus_window to use for non-X window focus --- include/xwayland/xwm.h | 1 + xwayland/xwm.c | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/include/xwayland/xwm.h b/include/xwayland/xwm.h index 799cebc47..d38e5cc91 100644 --- a/include/xwayland/xwm.h +++ b/include/xwayland/xwm.h @@ -115,6 +115,7 @@ struct wlr_xwm { xcb_connection_t *xcb_conn; xcb_screen_t *screen; xcb_window_t window; + xcb_window_t no_focus_window; xcb_visualid_t visual_id; xcb_colormap_t colormap; xcb_render_pictformat_t render_format_id; diff --git a/xwayland/xwm.c b/xwayland/xwm.c index d5a57d5e5..953906fa8 100644 --- a/xwayland/xwm.c +++ b/xwayland/xwm.c @@ -2204,6 +2204,9 @@ void xwm_destroy(struct wlr_xwm *xwm) { if (xwm->colormap) { xcb_free_colormap(xwm->xcb_conn, xwm->colormap); } + if (xwm->no_focus_window) { + xcb_destroy_window(xwm->xcb_conn, xwm->no_focus_window); + } if (xwm->window) { xcb_destroy_window(xwm->xcb_conn, xwm->window); } @@ -2361,6 +2364,31 @@ static void xwm_create_wm_window(struct wlr_xwm *xwm) { XCB_CURRENT_TIME); } +static void xwm_create_no_focus_window(struct wlr_xwm *xwm) { + xwm->no_focus_window = xcb_generate_id(xwm->xcb_conn); + + uint32_t values[2] = { + 1, + XCB_EVENT_MASK_KEY_PRESS | + XCB_EVENT_MASK_KEY_RELEASE | + XCB_EVENT_MASK_FOCUS_CHANGE + }; + xcb_create_window(xwm->xcb_conn, + XCB_COPY_FROM_PARENT, + xwm->no_focus_window, + xwm->screen->root, + -100, -100, + 1, 1, + 0, + XCB_WINDOW_CLASS_COPY_FROM_PARENT, + XCB_COPY_FROM_PARENT, + XCB_CW_OVERRIDE_REDIRECT | + XCB_CW_EVENT_MASK, + values); + + xcb_map_window(xwm->xcb_conn, xwm->no_focus_window); +} + // TODO use me to support 32 bit color somehow static void xwm_get_visual_and_colormap(struct wlr_xwm *xwm) { xcb_depth_iterator_t d_iter; @@ -2587,6 +2615,7 @@ struct wlr_xwm *xwm_create(struct wlr_xwayland *xwayland, int wm_fd) { &xwm->shell_v1_destroy); xwm_create_wm_window(xwm); + xwm_create_no_focus_window(xwm); xcb_flush(xwm->xcb_conn); From 6c782251608e4f399d115af0d5c302f192e31df8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Bernon?= Date: Thu, 22 May 2025 11:43:18 +0200 Subject: [PATCH 014/311] xwayland: Activate no_focus_window when a Wayland window is activated None active window might be interpreted from an X point of view as a transient focus state, and is used by multiple X window managers when a temporary focus change is in progress, or simply when grabbing the keyboard. From Wine side, we translate any active window change to the Win32 application, and handling None active window as an actual window deactivation and focus loss creates spurious events and an undesired feedback loop, as apps might react to it. We still want to be able to detect actual focus loss under an XWayland session, and having XWayland window manager focus an actual X window instead will make the distinction possible. --- xwayland/xwm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xwayland/xwm.c b/xwayland/xwm.c index 953906fa8..0225a0d05 100644 --- a/xwayland/xwm.c +++ b/xwayland/xwm.c @@ -441,7 +441,7 @@ static void xwm_set_focused_window(struct wlr_xwm *xwm, xsurface_set_net_wm_state(xsurface); xwm_set_net_active_window(xwm, xsurface->window_id); } else { - xwm_set_net_active_window(xwm, XCB_WINDOW_NONE); + xwm_set_net_active_window(xwm, xwm->no_focus_window); } } From 37992cf3b89bc603f3073ced116a692860fd3992 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Thu, 5 Jun 2025 18:17:56 +0200 Subject: [PATCH 015/311] idle_notify_v1: drop trailing spaces --- types/wlr_idle_notify_v1.c | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/types/wlr_idle_notify_v1.c b/types/wlr_idle_notify_v1.c index b4b9afcc1..0488225a8 100644 --- a/types/wlr_idle_notify_v1.c +++ b/types/wlr_idle_notify_v1.c @@ -82,8 +82,7 @@ static void notification_destroy(struct wlr_idle_notification_v1 *notification) } static void notification_reset_timer(struct wlr_idle_notification_v1 *notification) { - if (notification->notifier->inhibited && - notification->obey_inhibitors) { + if (notification->notifier->inhibited && notification->obey_inhibitors) { notification_set_idle(notification, false); if (notification->timer != NULL) { wl_event_source_timer_update(notification->timer, 0); @@ -177,7 +176,7 @@ static void notifier_handle_get_input_idle_notification( struct wl_client *client, struct wl_resource *notifier_resource, uint32_t id, uint32_t timeout, struct wl_resource *seat_resource) { - construct_idle_notification(client, notifier_resource, id, + construct_idle_notification(client, notifier_resource, id, timeout, seat_resource, false); } @@ -191,10 +190,8 @@ static void notifier_handle_get_idle_notification( static const struct ext_idle_notifier_v1_interface notifier_impl = { .destroy = resource_handle_destroy, - .get_idle_notification = - notifier_handle_get_idle_notification, - .get_input_idle_notification = - notifier_handle_get_input_idle_notification, + .get_idle_notification = notifier_handle_get_idle_notification, + .get_input_idle_notification = notifier_handle_get_input_idle_notification, }; static void notifier_bind(struct wl_client *client, void *data, @@ -257,12 +254,9 @@ void wlr_idle_notifier_v1_set_inhibited(struct wlr_idle_notifier_v1 *notifier, void wlr_idle_notifier_v1_notify_activity(struct wlr_idle_notifier_v1 *notifier, struct wlr_seat *seat) { - struct wlr_idle_notification_v1 *notification; wl_list_for_each(notification, ¬ifier->notifications, link) { - if (notification->seat == seat && - !(notifier->inhibited && - notification->obey_inhibitors)) { + if (notification->seat == seat && !(notifier->inhibited && notification->obey_inhibitors)) { notification_handle_activity(notification); } } From 221b37355f0e57175dd881f154a877aaddab0080 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Thu, 1 May 2025 18:32:19 +0200 Subject: [PATCH 016/311] xwayland: require xcb-xfixes 1.15 xcb-xfixes 1.15 was released back in 2022. Let's simplify our build setup by requiring it. --- xwayland/meson.build | 24 +++++++++++------------- xwayland/xwayland.c | 2 -- xwayland/xwm.c | 3 --- 3 files changed, 11 insertions(+), 18 deletions(-) diff --git a/xwayland/meson.build b/xwayland/meson.build index 81cb018eb..3d29dc6c5 100644 --- a/xwayland/meson.build +++ b/xwayland/meson.build @@ -1,13 +1,13 @@ xwayland_libs = [] -xwayland_required = [ - 'xcb', - 'xcb-composite', - 'xcb-ewmh', - 'xcb-icccm', - 'xcb-render', - 'xcb-res', - 'xcb-xfixes', -] +xwayland_required = { + 'xcb': [], + 'xcb-composite': [], + 'xcb-ewmh': [], + 'xcb-icccm': [], + 'xcb-render': [], + 'xcb-res': [], + 'xcb-xfixes': '>=1.15', +} xwayland_optional = { 'xcb-errors': 'Required for printing X11 errors.', } @@ -37,8 +37,9 @@ if not xwayland.found() subdir_done() endif -foreach lib : xwayland_required +foreach lib, version : xwayland_required dep = dependency(lib, + version: version, required: get_option('xwayland'), not_found_message: '\n'.join(msg).format(lib), ) @@ -93,6 +94,3 @@ wlr_files += files( ) wlr_deps += xwayland_libs features += { 'xwayland': true } - -have = cc.has_function('xcb_xfixes_set_client_disconnect_mode', dependencies: xwayland_libs) -internal_config.set10('HAVE_XCB_XFIXES_SET_CLIENT_DISCONNECT_MODE', have) diff --git a/xwayland/xwayland.c b/xwayland/xwayland.c index d3b2a6c84..d25a132b5 100644 --- a/xwayland/xwayland.c +++ b/xwayland/xwayland.c @@ -145,9 +145,7 @@ struct wlr_xwayland *wlr_xwayland_create(struct wl_display *wl_display, struct wlr_xwayland_server_options options = { .lazy = lazy, .enable_wm = true, -#if HAVE_XCB_XFIXES_SET_CLIENT_DISCONNECT_MODE .terminate_delay = lazy ? 10 : 0, -#endif }; struct wlr_xwayland_server *server = wlr_xwayland_server_create(wl_display, &options); if (server == NULL) { diff --git a/xwayland/xwm.c b/xwayland/xwm.c index 0225a0d05..bcdcd218c 100644 --- a/xwayland/xwm.c +++ b/xwayland/xwm.c @@ -1,7 +1,6 @@ #include #include #include -#include #include #include #include @@ -2584,13 +2583,11 @@ struct wlr_xwm *xwm_create(struct wlr_xwayland *xwayland, int wm_fd) { sizeof(supported)/sizeof(*supported), supported); -#if HAVE_XCB_XFIXES_SET_CLIENT_DISCONNECT_MODE if (xwm->xwayland->server->options.terminate_delay > 0 && xwm->xfixes_major_version >= 6) { xcb_xfixes_set_client_disconnect_mode(xwm->xcb_conn, XCB_XFIXES_CLIENT_DISCONNECT_FLAGS_TERMINATE); } -#endif xcb_flush(xwm->xcb_conn); From bb50c7a5a4244ba70c38c99f92f86766359f69f9 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Thu, 1 May 2025 18:42:26 +0200 Subject: [PATCH 017/311] render/allocator/gbm: require GBM 21.1 Mesa 21.1 was released back in 2021. Let's require it so that we can simplify our build and remove the workaround. --- render/allocator/gbm.c | 29 ----------------------------- render/allocator/meson.build | 5 +---- 2 files changed, 1 insertion(+), 33 deletions(-) diff --git a/render/allocator/gbm.c b/render/allocator/gbm.c index 3e157bb69..ca16177d1 100644 --- a/render/allocator/gbm.c +++ b/render/allocator/gbm.c @@ -9,7 +9,6 @@ #include #include -#include "config.h" #include "render/allocator/gbm.h" #include "render/drm_format_set.h" @@ -39,40 +38,12 @@ static bool export_gbm_bo(struct gbm_bo *bo, attribs.modifier = gbm_bo_get_modifier(bo); int i; - int32_t handle = -1; for (i = 0; i < attribs.n_planes; ++i) { -#if HAVE_GBM_BO_GET_FD_FOR_PLANE - (void)handle; - attribs.fd[i] = gbm_bo_get_fd_for_plane(bo, i); if (attribs.fd[i] < 0) { wlr_log(WLR_ERROR, "gbm_bo_get_fd_for_plane failed"); goto error_fd; } -#else - // GBM is lacking a function to get a FD for a given plane. Instead, - // check all planes have the same handle. We can't use - // drmPrimeHandleToFD because that messes up handle ref'counting in - // the user-space driver. - union gbm_bo_handle plane_handle = gbm_bo_get_handle_for_plane(bo, i); - if (plane_handle.s32 < 0) { - wlr_log(WLR_ERROR, "gbm_bo_get_handle_for_plane failed"); - goto error_fd; - } - if (i == 0) { - handle = plane_handle.s32; - } else if (plane_handle.s32 != handle) { - wlr_log(WLR_ERROR, "Failed to export GBM BO: " - "all planes don't have the same GEM handle"); - goto error_fd; - } - - attribs.fd[i] = gbm_bo_get_fd(bo); - if (attribs.fd[i] < 0) { - wlr_log(WLR_ERROR, "gbm_bo_get_fd failed"); - goto error_fd; - } -#endif attribs.offset[i] = gbm_bo_get_offset(bo, i); attribs.stride[i] = gbm_bo_get_stride_for_plane(bo, i); diff --git a/render/allocator/meson.build b/render/allocator/meson.build index c7559b967..b812f6941 100644 --- a/render/allocator/meson.build +++ b/render/allocator/meson.build @@ -13,15 +13,12 @@ wlr_files += files( gbm = disabler() if 'gbm' in allocators or 'auto' in allocators - gbm = dependency('gbm', version: '>=17.1.0', required: 'gbm' in allocators) + gbm = dependency('gbm', version: '>=21.1', required: 'gbm' in allocators) endif if gbm.found() wlr_files += files('gbm.c') wlr_deps += gbm features += { 'gbm-allocator': true } - - has = cc.has_function('gbm_bo_get_fd_for_plane', dependencies: [gbm]) - internal_config.set10('HAVE_GBM_BO_GET_FD_FOR_PLANE', has) endif udmabuf = false From 8fb4e4dabb1526b939003345dab2a9744018b056 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Fri, 30 May 2025 23:10:39 +0200 Subject: [PATCH 018/311] swapchain: assert that size is not empty at creation time Failing later (at buffer allocation time) makes it more difficult to track down where the issue comes from. --- render/swapchain.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/render/swapchain.c b/render/swapchain.c index e87a99ead..24d6f1a87 100644 --- a/render/swapchain.c +++ b/render/swapchain.c @@ -18,6 +18,8 @@ static void swapchain_handle_allocator_destroy(struct wl_listener *listener, struct wlr_swapchain *wlr_swapchain_create( struct wlr_allocator *alloc, int width, int height, const struct wlr_drm_format *format) { + assert(width > 0 && height > 0); + struct wlr_swapchain *swapchain = calloc(1, sizeof(*swapchain)); if (swapchain == NULL) { return NULL; From b066fd6b5a80a20c7c2ec35dbfb8d534b301a280 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sun, 25 May 2025 16:35:52 +0200 Subject: [PATCH 019/311] ext_image_capture_source_v1: add support for foreign toplevels --- .../types/wlr_ext_image_capture_source_v1.h | 32 +++++ .../foreign_toplevel.c | 115 ++++++++++++++++++ types/meson.build | 1 + 3 files changed, 148 insertions(+) create mode 100644 types/ext_image_capture_source_v1/foreign_toplevel.c diff --git a/include/wlr/types/wlr_ext_image_capture_source_v1.h b/include/wlr/types/wlr_ext_image_capture_source_v1.h index 8d80a9e3c..047e73df6 100644 --- a/include/wlr/types/wlr_ext_image_capture_source_v1.h +++ b/include/wlr/types/wlr_ext_image_capture_source_v1.h @@ -79,6 +79,31 @@ struct wlr_ext_output_image_capture_source_manager_v1 { } WLR_PRIVATE; }; +/** + * Interface exposing one screen capture source per foreign toplevel. + */ +struct wlr_ext_foreign_toplevel_image_capture_source_manager_v1 { + struct wl_global *global; + + struct { + struct wl_signal destroy; + struct wl_signal new_request; // struct wlr_ext_foreign_toplevel_image_capture_source_manager_v1_request + } events; + + struct { + struct wl_listener display_destroy; + } WLR_PRIVATE; +}; + +struct wlr_ext_foreign_toplevel_image_capture_source_manager_v1_request { + struct wlr_ext_foreign_toplevel_handle_v1 *toplevel_handle; + struct wl_client *client; + + struct { + uint32_t new_id; + } WLR_PRIVATE; +}; + /** * Obtain a struct wlr_ext_image_capture_source_v1 from an ext_image_capture_source_v1 * resource. @@ -91,4 +116,11 @@ struct wlr_ext_image_capture_source_v1 *wlr_ext_image_capture_source_v1_from_res struct wlr_ext_output_image_capture_source_manager_v1 *wlr_ext_output_image_capture_source_manager_v1_create( struct wl_display *display, uint32_t version); +struct wlr_ext_foreign_toplevel_image_capture_source_manager_v1 * +wlr_ext_foreign_toplevel_image_capture_source_manager_v1_create(struct wl_display *display, uint32_t version); + +bool wlr_ext_foreign_toplevel_image_capture_source_manager_v1_request_accept( + struct wlr_ext_foreign_toplevel_image_capture_source_manager_v1_request *request, + struct wlr_ext_image_capture_source_v1 *source); + #endif diff --git a/types/ext_image_capture_source_v1/foreign_toplevel.c b/types/ext_image_capture_source_v1/foreign_toplevel.c new file mode 100644 index 000000000..c79156bce --- /dev/null +++ b/types/ext_image_capture_source_v1/foreign_toplevel.c @@ -0,0 +1,115 @@ +#include +#include +#include +#include +#include "ext-image-capture-source-v1-protocol.h" + +#define FOREIGN_TOPLEVEL_IMAGE_SOURCE_MANAGER_V1_VERSION 1 + +struct wlr_ext_foreign_toplevel_image_capture_source_v1 { + struct wlr_ext_image_capture_source_v1 base; +}; + +static const struct ext_foreign_toplevel_image_capture_source_manager_v1_interface foreign_toplevel_manager_impl; + +static struct wlr_ext_foreign_toplevel_image_capture_source_manager_v1 * +foreign_toplevel_manager_from_resource(struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &ext_foreign_toplevel_image_capture_source_manager_v1_interface, + &foreign_toplevel_manager_impl)); + return wl_resource_get_user_data(resource); +} + +bool wlr_ext_foreign_toplevel_image_capture_source_manager_v1_request_accept( + struct wlr_ext_foreign_toplevel_image_capture_source_manager_v1_request *request, + struct wlr_ext_image_capture_source_v1 *source) { + return wlr_ext_image_capture_source_v1_create_resource(source, request->client, request->new_id); +} + +static void foreign_toplevel_manager_handle_create_source(struct wl_client *client, + struct wl_resource *manager_resource, uint32_t new_id, + struct wl_resource *foreign_toplevel_resource) { + struct wlr_ext_foreign_toplevel_image_capture_source_manager_v1 *manager = + foreign_toplevel_manager_from_resource(manager_resource); + struct wlr_ext_foreign_toplevel_handle_v1 *toplevel_handle = + wlr_ext_foreign_toplevel_handle_v1_from_resource(foreign_toplevel_resource); + if (toplevel_handle == NULL) { + wlr_ext_image_capture_source_v1_create_resource(NULL, client, new_id); + return; + } + + struct wlr_ext_foreign_toplevel_image_capture_source_manager_v1_request *request = + calloc(1, sizeof(*request)); + if (request == NULL) { + wl_resource_post_no_memory(manager_resource); + return; + } + + request->toplevel_handle = toplevel_handle; + request->client = client; + request->new_id = new_id; + + wl_signal_emit_mutable(&manager->events.new_request, request); +} + +static void foreign_toplevel_manager_handle_destroy(struct wl_client *client, + struct wl_resource *manager_resource) { + wl_resource_destroy(manager_resource); +} + +static const struct ext_foreign_toplevel_image_capture_source_manager_v1_interface foreign_toplevel_manager_impl = { + .create_source = foreign_toplevel_manager_handle_create_source, + .destroy = foreign_toplevel_manager_handle_destroy, +}; + +static void foreign_toplevel_manager_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) { + struct wlr_ext_foreign_toplevel_image_capture_source_manager_v1 *manager = data; + + struct wl_resource *resource = wl_resource_create(client, + &ext_foreign_toplevel_image_capture_source_manager_v1_interface, version, id); + if (!resource) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &foreign_toplevel_manager_impl, manager, NULL); +} + +static void foreign_toplevel_manager_handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_ext_foreign_toplevel_image_capture_source_manager_v1 *manager = + wl_container_of(listener, manager, display_destroy); + wl_signal_emit_mutable(&manager->events.destroy, NULL); + assert(wl_list_empty(&manager->events.destroy.listener_list)); + assert(wl_list_empty(&manager->events.new_request.listener_list)); + wl_list_remove(&manager->display_destroy.link); + wl_global_destroy(manager->global); + free(manager); +} + +struct wlr_ext_foreign_toplevel_image_capture_source_manager_v1 * +wlr_ext_foreign_toplevel_image_capture_source_manager_v1_create(struct wl_display *display, + uint32_t version) { + assert(version <= FOREIGN_TOPLEVEL_IMAGE_SOURCE_MANAGER_V1_VERSION); + + struct wlr_ext_foreign_toplevel_image_capture_source_manager_v1 *manager = + calloc(1, sizeof(*manager)); + if (manager == NULL) { + return NULL; + } + + manager->global = wl_global_create(display, + &ext_foreign_toplevel_image_capture_source_manager_v1_interface, + version, manager, foreign_toplevel_manager_bind); + if (manager->global == NULL) { + free(manager); + return NULL; + } + + wl_signal_init(&manager->events.destroy); + wl_signal_init(&manager->events.new_request); + + manager->display_destroy.notify = foreign_toplevel_manager_handle_display_destroy; + wl_display_add_destroy_listener(display, &manager->display_destroy); + + return manager; +} diff --git a/types/meson.build b/types/meson.build index 831698eb9..b08a566a4 100644 --- a/types/meson.build +++ b/types/meson.build @@ -5,6 +5,7 @@ wlr_files += files( 'data_device/wlr_drag.c', 'ext_image_capture_source_v1/base.c', 'ext_image_capture_source_v1/output.c', + 'ext_image_capture_source_v1/foreign_toplevel.c', 'output/cursor.c', 'output/output.c', 'output/render.c', From da820070f4cdb15c53e5a54177cc6c2689d40438 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sun, 25 May 2025 19:33:57 +0200 Subject: [PATCH 020/311] ext_image_capture_source_v1: add helper to capture scene nodes --- include/types/wlr_scene.h | 2 + .../types/wlr_ext_image_capture_source_v1.h | 8 + types/ext_image_capture_source_v1/scene.c | 325 ++++++++++++++++++ types/meson.build | 1 + types/scene/wlr_scene.c | 5 +- 5 files changed, 337 insertions(+), 4 deletions(-) create mode 100644 types/ext_image_capture_source_v1/scene.c diff --git a/include/types/wlr_scene.h b/include/types/wlr_scene.h index 80dcfd1bd..c4b40cdd0 100644 --- a/include/types/wlr_scene.h +++ b/include/types/wlr_scene.h @@ -5,6 +5,8 @@ struct wlr_scene *scene_node_get_root(struct wlr_scene_node *node); +void scene_node_get_size(struct wlr_scene_node *node, int *width, int *height); + void scene_surface_set_clip(struct wlr_scene_surface *surface, struct wlr_box *clip); #endif diff --git a/include/wlr/types/wlr_ext_image_capture_source_v1.h b/include/wlr/types/wlr_ext_image_capture_source_v1.h index 047e73df6..e38f68795 100644 --- a/include/wlr/types/wlr_ext_image_capture_source_v1.h +++ b/include/wlr/types/wlr_ext_image_capture_source_v1.h @@ -13,6 +13,10 @@ #include #include +struct wlr_scene_node; +struct wlr_allocator; +struct wlr_renderer; + /** * A screen capture source. * @@ -123,4 +127,8 @@ bool wlr_ext_foreign_toplevel_image_capture_source_manager_v1_request_accept( struct wlr_ext_foreign_toplevel_image_capture_source_manager_v1_request *request, struct wlr_ext_image_capture_source_v1 *source); +struct wlr_ext_image_capture_source_v1 *wlr_ext_image_capture_source_v1_create_with_scene_node( + struct wlr_scene_node *node, struct wl_event_loop *event_loop, + struct wlr_allocator *allocator, struct wlr_renderer *renderer); + #endif diff --git a/types/ext_image_capture_source_v1/scene.c b/types/ext_image_capture_source_v1/scene.c new file mode 100644 index 000000000..a8bce9d3d --- /dev/null +++ b/types/ext_image_capture_source_v1/scene.c @@ -0,0 +1,325 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "types/wlr_output.h" +#include "types/wlr_scene.h" + +struct scene_node_source { + struct wlr_ext_image_capture_source_v1 base; + + struct wlr_scene_node *node; + + struct wlr_backend backend; + struct wlr_output output; + struct wlr_scene_output *scene_output; + + struct wl_event_source *idle_frame; + + struct wl_listener node_destroy; + struct wl_listener scene_output_destroy; + struct wl_listener output_frame; +}; + +struct scene_node_source_frame_event { + struct wlr_ext_image_capture_source_v1_frame_event base; + struct wlr_buffer *buffer; + struct timespec when; +}; + +static size_t last_output_num = 0; + +static void _get_scene_node_extents(struct wlr_scene_node *node, struct wlr_box *box, int lx, int ly) { + switch (node->type) { + case WLR_SCENE_NODE_TREE:; + struct wlr_scene_tree *scene_tree = wlr_scene_tree_from_node(node); + struct wlr_scene_node *child; + wl_list_for_each(child, &scene_tree->children, link) { + _get_scene_node_extents(child, box, lx + child->x, ly + child->y); + } + break; + case WLR_SCENE_NODE_RECT: + case WLR_SCENE_NODE_BUFFER:; + struct wlr_box node_box = { .x = lx, .y = ly }; + scene_node_get_size(node, &node_box.width, &node_box.height); + + if (node_box.x < box->x) { + box->x = node_box.x; + } + if (node_box.y < box->y) { + box->y = node_box.y; + } + if (node_box.x + node_box.width > box->x + box->width) { + box->width = node_box.x + node_box.width - box->x; + } + if (node_box.y + node_box.height > box->y + box->height) { + box->height = node_box.y + node_box.height - box->y; + } + break; + } +} + +static void get_scene_node_extents(struct wlr_scene_node *node, struct wlr_box *box) { + *box = (struct wlr_box){ .x = INT_MAX, .y = INT_MAX }; + int lx = 0, ly = 0; + wlr_scene_node_coords(node, &lx, &ly); + _get_scene_node_extents(node, box, lx, ly); +} + +static void source_render(struct scene_node_source *source) { + struct wlr_scene_output *scene_output = source->scene_output; + + struct wlr_box extents; + get_scene_node_extents(source->node, &extents); + + if (extents.width == 0 || extents.height == 0) { + return; + } + + wlr_scene_output_set_position(scene_output, extents.x, extents.y); + + struct wlr_output_state state; + wlr_output_state_init(&state); + wlr_output_state_set_enabled(&state, true); + wlr_output_state_set_custom_mode(&state, extents.width, extents.height, 0); + bool ok = wlr_scene_output_build_state(scene_output, &state, NULL) && + wlr_output_commit_state(scene_output->output, &state); + wlr_output_state_finish(&state); + + if (!ok) { + // TODO: send failure + return; + } + + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + wlr_scene_output_send_frame_done(scene_output, &now); +} + +static void source_start(struct wlr_ext_image_capture_source_v1 *base, bool with_cursors) { + struct scene_node_source *source = wl_container_of(base, source, base); + source_render(source); +} + +static void source_stop(struct wlr_ext_image_capture_source_v1 *base) { + struct scene_node_source *source = wl_container_of(base, source, base); + + struct wlr_output_state state; + wlr_output_state_init(&state); + wlr_output_state_set_enabled(&state, false); + wlr_output_commit_state(&source->output, &state); + wlr_output_state_finish(&state); +} + +static void source_schedule_frame(struct wlr_ext_image_capture_source_v1 *base) { + struct scene_node_source *source = wl_container_of(base, source, base); + wlr_output_update_needs_frame(&source->output); +} + +static void source_copy_frame(struct wlr_ext_image_capture_source_v1 *base, + struct wlr_ext_image_copy_capture_frame_v1 *frame, + struct wlr_ext_image_capture_source_v1_frame_event *base_event) { + struct scene_node_source *source = wl_container_of(base, source, base); + struct scene_node_source_frame_event *event = wl_container_of(base_event, event, base); + + if (wlr_ext_image_copy_capture_frame_v1_copy_buffer(frame, + event->buffer, source->output.renderer)) { + wlr_ext_image_copy_capture_frame_v1_ready(frame, + source->output.transform, &event->when); + } +} + +static const struct wlr_ext_image_capture_source_v1_interface source_impl = { + .start = source_start, + .stop = source_stop, + .schedule_frame = source_schedule_frame, + .copy_frame = source_copy_frame, +}; + +static const struct wlr_backend_impl backend_impl = {0}; + +static void source_update_buffer_constraints(struct scene_node_source *source, + const struct wlr_output_state *state) { + struct wlr_output *output = &source->output; + + if (!wlr_output_configure_primary_swapchain(output, state, &output->swapchain)) { + return; + } + + wlr_ext_image_capture_source_v1_set_constraints_from_swapchain(&source->base, + output->swapchain, output->renderer); +} + +static void source_handle_idle_frame(void *data) { + struct scene_node_source *source = data; + source->idle_frame = NULL; + wlr_output_send_frame(&source->output); +} + +static bool output_test(struct wlr_output *output, const struct wlr_output_state *state) { + struct scene_node_source *source = wl_container_of(output, source, output); + + uint32_t supported = + WLR_OUTPUT_STATE_BACKEND_OPTIONAL | + WLR_OUTPUT_STATE_BUFFER | + WLR_OUTPUT_STATE_ENABLED | + WLR_OUTPUT_STATE_MODE; + if ((state->committed & ~supported) != 0) { + return false; + } + + if (state->committed & WLR_OUTPUT_STATE_BUFFER) { + int pending_width, pending_height; + output_pending_resolution(output, state, + &pending_width, &pending_height); + if (state->buffer->width != pending_width || + state->buffer->height != pending_height) { + return false; + } + struct wlr_fbox src_box; + output_state_get_buffer_src_box(state, &src_box); + if (src_box.x != 0.0 || src_box.y != 0.0 || + src_box.width != (double)state->buffer->width || + src_box.height != (double)state->buffer->height) { + return false; + } + } + + return true; +} + +static bool output_commit(struct wlr_output *output, const struct wlr_output_state *state) { + struct scene_node_source *source = wl_container_of(output, source, output); + + if (source->idle_frame != NULL) { + wlr_log(WLR_DEBUG, "Failed to commit capture output: a frame is still pending"); + return false; + } + + if ((state->committed & WLR_OUTPUT_STATE_ENABLED) && !state->enabled) { + return true; + } + + if (state->committed & WLR_OUTPUT_STATE_MODE) { + source_update_buffer_constraints(source, state); + } + + if (!(state->committed & WLR_OUTPUT_STATE_BUFFER)) { + wlr_log(WLR_DEBUG, "Failed to commit capture output: missing buffer"); + return false; + } + + struct wlr_buffer *buffer = state->buffer; + + pixman_region32_t full_damage; + pixman_region32_init_rect(&full_damage, 0, 0, buffer->width, buffer->height); + + const pixman_region32_t *damage; + if (state->committed & WLR_OUTPUT_STATE_DAMAGE) { + damage = &state->damage; + } else { + damage = &full_damage; + } + + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + + struct scene_node_source_frame_event frame_event = { + .base = { .damage = damage }, + .buffer = buffer, + .when = now, + }; + wl_signal_emit_mutable(&source->base.events.frame, &frame_event.base); + + pixman_region32_fini(&full_damage); + + source->idle_frame = + wl_event_loop_add_idle(output->event_loop, source_handle_idle_frame, source); + + return true; +} + +static const struct wlr_output_impl output_impl = { + .test = output_test, + .commit = output_commit, +}; + +static void source_destroy(struct scene_node_source *source) { + wl_list_remove(&source->node_destroy.link); + wl_list_remove(&source->scene_output_destroy.link); + wl_list_remove(&source->output_frame.link); + wlr_ext_image_capture_source_v1_finish(&source->base); + wlr_scene_output_destroy(source->scene_output); + wlr_output_finish(&source->output); + wlr_backend_finish(&source->backend); + free(source); +} + +static void source_handle_node_destroy(struct wl_listener *listener, void *data) { + struct scene_node_source *source = wl_container_of(listener, source, node_destroy); + source_destroy(source); +} + +static void source_handle_scene_output_destroy(struct wl_listener *listener, void *data) { + struct scene_node_source *source = wl_container_of(listener, source, scene_output_destroy); + source->scene_output = NULL; + wl_list_remove(&source->scene_output_destroy.link); + wl_list_init(&source->scene_output_destroy.link); +} + +static void source_handle_output_frame(struct wl_listener *listener, void *data) { + struct scene_node_source *source = wl_container_of(listener, source, output_frame); + if (source->scene_output == NULL) { + return; + } + + if (!wlr_scene_output_needs_frame(source->scene_output)) { + return; + } + + source_render(source); +} + +struct wlr_ext_image_capture_source_v1 *wlr_ext_image_capture_source_v1_create_with_scene_node( + struct wlr_scene_node *node, struct wl_event_loop *event_loop, + struct wlr_allocator *allocator, struct wlr_renderer *renderer) { + struct scene_node_source *source = calloc(1, sizeof(*source)); + if (source == NULL) { + return NULL; + } + + source->node = node; + + wlr_ext_image_capture_source_v1_init(&source->base, &source_impl); + + wlr_backend_init(&source->backend, &backend_impl); + source->backend.buffer_caps = WLR_BUFFER_CAP_DMABUF | WLR_BUFFER_CAP_SHM; + + wlr_output_init(&source->output, &source->backend, &output_impl, event_loop, NULL); + + size_t output_num = ++last_output_num; + char name[64]; + snprintf(name, sizeof(name), "CAPTURE-%zu", output_num); + wlr_output_set_name(&source->output, name); + + wlr_output_init_render(&source->output, allocator, renderer); + + struct wlr_scene *scene = scene_node_get_root(node); + source->scene_output = wlr_scene_output_create(scene, &source->output); + + source->node_destroy.notify = source_handle_node_destroy; + wl_signal_add(&node->events.destroy, &source->node_destroy); + + source->scene_output_destroy.notify = source_handle_scene_output_destroy; + wl_signal_add(&source->scene_output->events.destroy, &source->scene_output_destroy); + + source->output_frame.notify = source_handle_output_frame; + wl_signal_add(&source->output.events.frame, &source->output_frame); + + return &source->base; +} diff --git a/types/meson.build b/types/meson.build index b08a566a4..43c4eb2ad 100644 --- a/types/meson.build +++ b/types/meson.build @@ -6,6 +6,7 @@ wlr_files += files( 'ext_image_capture_source_v1/base.c', 'ext_image_capture_source_v1/output.c', 'ext_image_capture_source_v1/foreign_toplevel.c', + 'ext_image_capture_source_v1/scene.c', 'output/cursor.c', 'output/output.c', 'output/render.c', diff --git a/types/scene/wlr_scene.c b/types/scene/wlr_scene.c index e823d62ae..1dba73f07 100644 --- a/types/scene/wlr_scene.c +++ b/types/scene/wlr_scene.c @@ -210,8 +210,6 @@ struct wlr_scene_tree *wlr_scene_tree_create(struct wlr_scene_tree *parent) { return tree; } -static void scene_node_get_size(struct wlr_scene_node *node, int *lx, int *ly); - typedef bool (*scene_node_box_iterator_func_t)(struct wlr_scene_node *node, int sx, int sy, void *data); @@ -1120,8 +1118,7 @@ static struct wlr_texture *scene_buffer_get_texture( return texture; } -static void scene_node_get_size(struct wlr_scene_node *node, - int *width, int *height) { +void scene_node_get_size(struct wlr_scene_node *node, int *width, int *height) { *width = 0; *height = 0; From 95b2771bfd096cd8b3a3212c1ba0e1026f410cc4 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Thu, 5 Jun 2025 11:45:56 +0200 Subject: [PATCH 021/311] scene: ignore outputs with too small intersection with nodes If a node has a very small intersection with an output, there's no point in trying to adapt the node's rendering to that output. --- types/scene/wlr_scene.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/types/scene/wlr_scene.c b/types/scene/wlr_scene.c index 1dba73f07..256d68648 100644 --- a/types/scene/wlr_scene.c +++ b/types/scene/wlr_scene.c @@ -424,6 +424,8 @@ static void update_node_update_outputs(struct wlr_scene_node *node, size_t count = 0; uint64_t active_outputs = 0; + uint32_t visible_area = region_area(&node->visible); + // let's update the outputs in two steps: // - the primary outputs // - the enter/leave signals @@ -451,9 +453,12 @@ static void update_node_update_outputs(struct wlr_scene_node *node, pixman_region32_init(&intersection); pixman_region32_intersect_rect(&intersection, &node->visible, output_box.x, output_box.y, output_box.width, output_box.height); + uint32_t overlap = region_area(&intersection); + pixman_region32_fini(&intersection); - if (!pixman_region32_empty(&intersection)) { - uint32_t overlap = region_area(&intersection); + // If the overlap accounts for less than 10% of the visible node area, + // ignore this output + if (overlap >= 0.1 * visible_area) { if (overlap >= largest_overlap) { largest_overlap = overlap; scene_buffer->primary_output = scene_output; @@ -462,8 +467,6 @@ static void update_node_update_outputs(struct wlr_scene_node *node, active_outputs |= 1ull << scene_output->index; count++; } - - pixman_region32_fini(&intersection); } if (old_primary_output != scene_buffer->primary_output) { From 8713ac72fb9126f2cbc3f6dfd7f4154ea3174553 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Thu, 5 Jun 2025 11:36:39 +0200 Subject: [PATCH 022/311] scene: configure clients with the highest output scale If a surface appears on two outputs with the same intersection area, or even if a surface appears on an output with a small intersection area, we want to use the highest scale. Fixes flip-flop when a surface is added to multiple scenes. References: https://gitlab.freedesktop.org/wlroots/wlroots/-/issues/3901 --- types/scene/surface.c | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/types/scene/surface.c b/types/scene/surface.c index e5b3b0f7d..d834aa71b 100644 --- a/types/scene/surface.c +++ b/types/scene/surface.c @@ -11,19 +11,30 @@ #include #include "types/wlr_scene.h" +static double get_surface_preferred_buffer_scale(struct wlr_surface *surface) { + double scale = 1; + struct wlr_surface_output *surface_output; + wl_list_for_each(surface_output, &surface->current_outputs, link) { + if (surface_output->output->scale > scale) { + scale = surface_output->output->scale; + } + } + return scale; +} + static void handle_scene_buffer_outputs_update( struct wl_listener *listener, void *data) { struct wlr_scene_surface *surface = wl_container_of(listener, surface, outputs_update); - if (surface->buffer->primary_output == NULL) { - return; - } - double scale = surface->buffer->primary_output->output->scale; + double scale = get_surface_preferred_buffer_scale(surface->surface); wlr_fractional_scale_v1_notify_scale(surface->surface, scale); wlr_surface_set_preferred_buffer_scale(surface->surface, ceil(scale)); - wlr_surface_set_preferred_buffer_transform(surface->surface, - surface->buffer->primary_output->output->transform); + + if (surface->buffer->primary_output != NULL) { + wlr_surface_set_preferred_buffer_transform(surface->surface, + surface->buffer->primary_output->output->transform); + } } static void handle_scene_buffer_output_enter( From 51d051497d9114e98e6004aa37bb1e2c537534fb Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Thu, 5 Jun 2025 12:11:01 +0200 Subject: [PATCH 023/311] scene: filter frame_done primary output in surface handler This lets the surface handler decide which output to send frame callbacks from. The output_sample event already works this way. Introduce wlr_scene_surface_send_frame_done() as a replacement for wlr_scene_buffer_send_frame_done() when a compositor doesn't have an output at hand. --- include/wlr/types/wlr_scene.h | 15 +++++++++++++-- types/scene/surface.c | 14 ++++++++++++-- types/scene/wlr_scene.c | 13 +++++++------ 3 files changed, 32 insertions(+), 10 deletions(-) diff --git a/include/wlr/types/wlr_scene.h b/include/wlr/types/wlr_scene.h index 2292f1fc8..9195264ab 100644 --- a/include/wlr/types/wlr_scene.h +++ b/include/wlr/types/wlr_scene.h @@ -152,6 +152,11 @@ struct wlr_scene_output_sample_event { bool direct_scanout; }; +struct wlr_scene_frame_done_event { + struct wlr_scene_output *output; + struct timespec when; +}; + /** A scene-graph node displaying a buffer */ struct wlr_scene_buffer { struct wlr_scene_node node; @@ -164,7 +169,7 @@ struct wlr_scene_buffer { struct wl_signal output_enter; // struct wlr_scene_output struct wl_signal output_leave; // struct wlr_scene_output struct wl_signal output_sample; // struct wlr_scene_output_sample_event - struct wl_signal frame_done; // struct timespec + struct wl_signal frame_done; // struct wlr_scene_frame_done_event } events; // May be NULL @@ -416,6 +421,12 @@ struct wlr_scene_rect *wlr_scene_rect_from_node(struct wlr_scene_node *node); struct wlr_scene_surface *wlr_scene_surface_try_from_buffer( struct wlr_scene_buffer *scene_buffer); +/** + * Call wlr_surface_send_frame_done() if the surface is visible. + */ +void wlr_scene_surface_send_frame_done(struct wlr_scene_surface *scene_surface, + const struct timespec *when); + /** * Add a node displaying a solid-colored rectangle to the scene-graph. * @@ -531,7 +542,7 @@ void wlr_scene_buffer_set_filter_mode(struct wlr_scene_buffer *scene_buffer, * Calls the buffer's frame_done signal. */ void wlr_scene_buffer_send_frame_done(struct wlr_scene_buffer *scene_buffer, - struct timespec *now); + struct wlr_scene_frame_done_event *event); /** * Add a viewport for the specified output to the scene-graph. diff --git a/types/scene/surface.c b/types/scene/surface.c index d834aa71b..f4bfe56ae 100644 --- a/types/scene/surface.c +++ b/types/scene/surface.c @@ -76,9 +76,19 @@ static void handle_scene_buffer_frame_done( struct wl_listener *listener, void *data) { struct wlr_scene_surface *surface = wl_container_of(listener, surface, frame_done); - struct timespec *now = data; + struct wlr_scene_frame_done_event *event = data; + if (surface->buffer->primary_output != event->output) { + return; + } - wlr_surface_send_frame_done(surface->surface, now); + wlr_surface_send_frame_done(surface->surface, &event->when); +} + +void wlr_scene_surface_send_frame_done(struct wlr_scene_surface *scene_surface, + const struct timespec *when) { + if (!pixman_region32_empty(&scene_surface->buffer->node.visible)) { + wlr_surface_send_frame_done(scene_surface->surface, when); + } } static void scene_surface_handle_surface_destroy( diff --git a/types/scene/wlr_scene.c b/types/scene/wlr_scene.c index 256d68648..f12f0d263 100644 --- a/types/scene/wlr_scene.c +++ b/types/scene/wlr_scene.c @@ -1072,9 +1072,9 @@ void wlr_scene_buffer_set_transform(struct wlr_scene_buffer *scene_buffer, } void wlr_scene_buffer_send_frame_done(struct wlr_scene_buffer *scene_buffer, - struct timespec *now) { + struct wlr_scene_frame_done_event *event) { if (!pixman_region32_empty(&scene_buffer->node.visible)) { - wl_signal_emit_mutable(&scene_buffer->events.frame_done, now); + wl_signal_emit_mutable(&scene_buffer->events.frame_done, event); } } @@ -2376,10 +2376,11 @@ static void scene_node_send_frame_done(struct wlr_scene_node *node, if (node->type == WLR_SCENE_NODE_BUFFER) { struct wlr_scene_buffer *scene_buffer = wlr_scene_buffer_from_node(node); - - if (scene_buffer->primary_output == scene_output) { - wlr_scene_buffer_send_frame_done(scene_buffer, now); - } + struct wlr_scene_frame_done_event event = { + .output = scene_output, + .when = *now, + }; + wlr_scene_buffer_send_frame_done(scene_buffer, &event); } else if (node->type == WLR_SCENE_NODE_TREE) { struct wlr_scene_tree *scene_tree = wlr_scene_tree_from_node(node); struct wlr_scene_node *child; From 6204fc3278b6db77b70ef75469733296c9a37be3 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Thu, 5 Jun 2025 12:29:55 +0200 Subject: [PATCH 024/311] scene: use output with highest refresh rate for frame pacing If a surface is mirrored on two outputs, we don't want to pick the first output if the second has a higher refresh rate. Also fixes duplicate frame/feedback events when a surface is added to multiple scenes. --- include/wlr/types/wlr_scene.h | 7 +++++-- types/scene/surface.c | 24 +++++++++++++++++++----- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/include/wlr/types/wlr_scene.h b/include/wlr/types/wlr_scene.h index 9195264ab..114d5c0f3 100644 --- a/include/wlr/types/wlr_scene.h +++ b/include/wlr/types/wlr_scene.h @@ -123,6 +123,10 @@ struct wlr_scene_surface { struct { struct wlr_box clip; + // Output used for frame pacing (surface frame callbacks, presentation + // time feedback, etc), may be NULL + struct wlr_output *frame_pacing_output; + struct wlr_addon addon; struct wl_listener outputs_update; @@ -178,8 +182,7 @@ struct wlr_scene_buffer { /** * The output that the largest area of this buffer is displayed on. * This may be NULL if the buffer is not currently displayed on any - * outputs. This is the output that should be used for frame callbacks, - * presentation feedback, etc. + * outputs. */ struct wlr_scene_output *primary_output; diff --git a/types/scene/surface.c b/types/scene/surface.c index f4bfe56ae..53a4b1069 100644 --- a/types/scene/surface.c +++ b/types/scene/surface.c @@ -22,11 +22,25 @@ static double get_surface_preferred_buffer_scale(struct wlr_surface *surface) { return scale; } +static struct wlr_output *get_surface_frame_pacing_output(struct wlr_surface *surface) { + struct wlr_output *frame_pacing_output = NULL; + struct wlr_surface_output *surface_output; + wl_list_for_each(surface_output, &surface->current_outputs, link) { + if (frame_pacing_output == NULL || + surface_output->output->refresh > frame_pacing_output->refresh) { + frame_pacing_output = surface_output->output; + } + } + return frame_pacing_output; +} + static void handle_scene_buffer_outputs_update( struct wl_listener *listener, void *data) { struct wlr_scene_surface *surface = wl_container_of(listener, surface, outputs_update); + surface->frame_pacing_output = get_surface_frame_pacing_output(surface->surface); + double scale = get_surface_preferred_buffer_scale(surface->surface); wlr_fractional_scale_v1_notify_scale(surface->surface, scale); wlr_surface_set_preferred_buffer_scale(surface->surface, ceil(scale)); @@ -60,15 +74,15 @@ static void handle_scene_buffer_output_sample( struct wlr_scene_surface *surface = wl_container_of(listener, surface, output_sample); const struct wlr_scene_output_sample_event *event = data; - struct wlr_scene_output *scene_output = event->output; - if (surface->buffer->primary_output != scene_output) { + struct wlr_output *output = event->output->output; + if (surface->frame_pacing_output != output) { return; } if (event->direct_scanout) { - wlr_presentation_surface_scanned_out_on_output(surface->surface, scene_output->output); + wlr_presentation_surface_scanned_out_on_output(surface->surface, output); } else { - wlr_presentation_surface_textured_on_output(surface->surface, scene_output->output); + wlr_presentation_surface_textured_on_output(surface->surface, output); } } @@ -77,7 +91,7 @@ static void handle_scene_buffer_frame_done( struct wlr_scene_surface *surface = wl_container_of(listener, surface, frame_done); struct wlr_scene_frame_done_event *event = data; - if (surface->buffer->primary_output != event->output) { + if (surface->frame_pacing_output != event->output->output) { return; } From c6133f9912b96b3f03ba30347678712c0776ad5c Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Thu, 5 Jun 2025 12:42:12 +0200 Subject: [PATCH 025/311] scene: send surface preferred transform alongside DMA-BUF feedback wl_surface.preferred_buffer_transform is mainly useful to make direct scan-out more likely. It shouldn't make a difference with GL/Vulkan rendering. --- types/scene/surface.c | 5 ----- types/scene/wlr_scene.c | 8 ++++++++ 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/types/scene/surface.c b/types/scene/surface.c index 53a4b1069..1973abcaa 100644 --- a/types/scene/surface.c +++ b/types/scene/surface.c @@ -44,11 +44,6 @@ static void handle_scene_buffer_outputs_update( double scale = get_surface_preferred_buffer_scale(surface->surface); wlr_fractional_scale_v1_notify_scale(surface->surface, scale); wlr_surface_set_preferred_buffer_scale(surface->surface, ceil(scale)); - - if (surface->buffer->primary_output != NULL) { - wlr_surface_set_preferred_buffer_transform(surface->surface, - surface->buffer->primary_output->output->transform); - } } static void handle_scene_buffer_output_enter( diff --git a/types/scene/wlr_scene.c b/types/scene/wlr_scene.c index f12f0d263..1acbb5650 100644 --- a/types/scene/wlr_scene.c +++ b/types/scene/wlr_scene.c @@ -1867,6 +1867,14 @@ static void scene_buffer_send_dmabuf_feedback(const struct wlr_scene *scene, return; } + enum wl_output_transform preferred_buffer_transform = WL_OUTPUT_TRANSFORM_NORMAL; + if (options->scanout_primary_output != NULL) { + preferred_buffer_transform = options->scanout_primary_output->transform; + } + + // TODO: also send wl_surface.preferred_buffer_transform when running with + // pure software rendering + wlr_surface_set_preferred_buffer_transform(surface->surface, preferred_buffer_transform); wlr_linux_dmabuf_v1_set_surface_feedback(scene->linux_dmabuf_v1, surface->surface, &feedback); From d421538b4acb69cc4064666bd9d0c23b37007fba Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Tue, 28 Jan 2025 09:57:41 +0100 Subject: [PATCH 026/311] render/color: add wlr_color_transform_init() --- include/render/color.h | 3 +++ render/color.c | 12 +++++++++--- render/color_lcms2.c | 4 +--- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/include/render/color.h b/include/render/color.h index cddf1e219..176abf3a7 100644 --- a/include/render/color.h +++ b/include/render/color.h @@ -38,6 +38,9 @@ struct wlr_color_transform_lut3d { size_t dim_len; }; +void wlr_color_transform_init(struct wlr_color_transform *tr, + enum wlr_color_transform_type type); + /** * Gets a wlr_color_transform_lut3d from a generic wlr_color_transform. * Asserts that the base type is COLOR_TRANSFORM_LUT_3D diff --git a/render/color.c b/render/color.c index c708938c7..a033619b5 100644 --- a/render/color.c +++ b/render/color.c @@ -21,14 +21,20 @@ static const struct wlr_color_primaries COLOR_PRIMARIES_BT2020 = { // code point .white = { 0.3127, 0.3290 }, }; +void wlr_color_transform_init(struct wlr_color_transform *tr, enum wlr_color_transform_type type) { + *tr = (struct wlr_color_transform){ + .type = type, + .ref_count = 1, + }; + wlr_addon_set_init(&tr->addons); +} + struct wlr_color_transform *wlr_color_transform_init_srgb(void) { struct wlr_color_transform *tx = calloc(1, sizeof(struct wlr_color_transform)); if (!tx) { return NULL; } - tx->type = COLOR_TRANSFORM_SRGB; - tx->ref_count = 1; - wlr_addon_set_init(&tx->addons); + wlr_color_transform_init(tx, COLOR_TRANSFORM_SRGB); return tx; } diff --git a/render/color_lcms2.c b/render/color_lcms2.c index c0f4db817..e6a2673e1 100644 --- a/render/color_lcms2.c +++ b/render/color_lcms2.c @@ -99,11 +99,9 @@ struct wlr_color_transform *wlr_color_transform_init_linear_to_icc( if (!tx) { goto out_lcms_tr; } - tx->base.type = COLOR_TRANSFORM_LUT_3D; + wlr_color_transform_init(&tx->base, COLOR_TRANSFORM_LUT_3D); tx->dim_len = dim_len; tx->lut_3d = lut_3d; - tx->base.ref_count = 1; - wlr_addon_set_init(&tx->base.addons); out_lcms_tr: cmsDeleteTransform(lcms_tr); From 9b97e2607d37186d749b4a07456294471014b465 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sat, 24 May 2025 11:10:22 +0200 Subject: [PATCH 027/311] render/color: use variable instead of type in sizeof() Conforms to the wlroots code style. --- render/color.c | 2 +- render/color_lcms2.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/render/color.c b/render/color.c index a033619b5..bbc07dc9f 100644 --- a/render/color.c +++ b/render/color.c @@ -30,7 +30,7 @@ void wlr_color_transform_init(struct wlr_color_transform *tr, enum wlr_color_tra } struct wlr_color_transform *wlr_color_transform_init_srgb(void) { - struct wlr_color_transform *tx = calloc(1, sizeof(struct wlr_color_transform)); + struct wlr_color_transform *tx = calloc(1, sizeof(*tx)); if (!tx) { return NULL; } diff --git a/render/color_lcms2.c b/render/color_lcms2.c index e6a2673e1..88df46087 100644 --- a/render/color_lcms2.c +++ b/render/color_lcms2.c @@ -95,7 +95,7 @@ struct wlr_color_transform *wlr_color_transform_init_linear_to_icc( } } - tx = calloc(1, sizeof(struct wlr_color_transform_lut3d)); + tx = calloc(1, sizeof(*tx)); if (!tx) { goto out_lcms_tr; } From 3665b53e296a69cf9f32f5099117ae30c84fca92 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sat, 24 May 2025 13:04:45 +0200 Subject: [PATCH 028/311] render/color: replace COLOR_TRANSFORM_LUT_3D with COLOR_TRANSFORM_LCMS2 Converting the LCMS2 transform to a 3D LUT early causes issues: - It's a lossy process, the consumer will not be able to pick a 3D LUT size on their own. - It requires unnecessary conversions and allocations: an intermediate 3D LUT is allocated, but the renderer already allocates one. - It makes it harder to support arbitrary color transforms in the renderer, because each type needs to be handled differently. Instead, expose a function to evaluate a color transform, and use that to build the 3D LUT in the renderer. --- include/render/color.h | 37 ++++++---------- include/render/vulkan.h | 1 + render/color.c | 13 +----- render/color_fallback.c | 17 +++++++- render/color_lcms2.c | 96 ++++++++++++++++++++--------------------- render/vulkan/pass.c | 56 +++++++++++++----------- 6 files changed, 109 insertions(+), 111 deletions(-) diff --git a/include/render/color.h b/include/render/color.h index 176abf3a7..a3ab73d83 100644 --- a/include/render/color.h +++ b/include/render/color.h @@ -7,7 +7,7 @@ enum wlr_color_transform_type { COLOR_TRANSFORM_SRGB, - COLOR_TRANSFORM_LUT_3D, + COLOR_TRANSFORM_LCMS2, }; struct wlr_color_transform { @@ -17,37 +17,24 @@ struct wlr_color_transform { enum wlr_color_transform_type type; }; -/** - * The formula is approximated via a 3D look-up table. A 3D LUT is a - * three-dimensional array where each element is an RGB triplet. The flat lut_3d - * array has a length of dim_len³. - * - * Color channel values in the range [0.0, 1.0] are mapped linearly to - * 3D LUT indices such that 0.0 maps exactly to the first element and 1.0 maps - * exactly to the last element in each dimension. - * - * The offset of the RGB triplet given red, green and blue indices r_index, - * g_index and b_index is: - * - * offset = 3 * (r_index + dim_len * g_index + dim_len * dim_len * b_index) - */ -struct wlr_color_transform_lut3d { - struct wlr_color_transform base; - - float *lut_3d; - size_t dim_len; -}; - void wlr_color_transform_init(struct wlr_color_transform *tr, enum wlr_color_transform_type type); /** - * Gets a wlr_color_transform_lut3d from a generic wlr_color_transform. - * Asserts that the base type is COLOR_TRANSFORM_LUT_3D + * Get a struct wlr_color_transform_lcms2 from a generic struct wlr_color_transform. + * Asserts that the base type is COLOR_TRANSFORM_LCMS2. */ -struct wlr_color_transform_lut3d *wlr_color_transform_lut3d_from_base( +struct wlr_color_transform_lcms2 *color_transform_lcms2_from_base( struct wlr_color_transform *tr); +void color_transform_lcms2_finish(struct wlr_color_transform_lcms2 *tr); + +/** + * Evaluate a LCMS2 color transform for a given RGB triplet. + */ +void color_transform_lcms2_eval(struct wlr_color_transform_lcms2 *tr, + float out[static 3], const float in[static 3]); + /** * Obtain primaries values from a well-known primaries name. */ diff --git a/include/render/vulkan.h b/include/render/vulkan.h index abeb11cc5..78109356f 100644 --- a/include/render/vulkan.h +++ b/include/render/vulkan.h @@ -516,6 +516,7 @@ struct wlr_vk_color_transform { struct wl_list link; // wlr_vk_renderer, list of all color transforms struct { + size_t dim; VkImage image; VkImageView image_view; VkDeviceMemory memory; diff --git a/render/color.c b/render/color.c index bbc07dc9f..1c8365013 100644 --- a/render/color.c +++ b/render/color.c @@ -42,10 +42,8 @@ static void color_transform_destroy(struct wlr_color_transform *tr) { switch (tr->type) { case COLOR_TRANSFORM_SRGB: break; - case COLOR_TRANSFORM_LUT_3D:; - struct wlr_color_transform_lut3d *lut3d = - wlr_color_transform_lut3d_from_base(tr); - free(lut3d->lut_3d); + case COLOR_TRANSFORM_LCMS2: + color_transform_lcms2_finish(color_transform_lcms2_from_base(tr)); break; } wlr_addon_set_finish(&tr->addons); @@ -68,13 +66,6 @@ void wlr_color_transform_unref(struct wlr_color_transform *tr) { } } -struct wlr_color_transform_lut3d *wlr_color_transform_lut3d_from_base( - struct wlr_color_transform *tr) { - assert(tr->type == COLOR_TRANSFORM_LUT_3D); - struct wlr_color_transform_lut3d *lut3d = wl_container_of(tr, lut3d, base); - return lut3d; -} - void wlr_color_primaries_from_named(struct wlr_color_primaries *out, enum wlr_color_named_primaries named) { switch (named) { diff --git a/render/color_fallback.c b/render/color_fallback.c index 72741c331..1b25881c4 100644 --- a/render/color_fallback.c +++ b/render/color_fallback.c @@ -1,5 +1,6 @@ -#include +#include #include +#include "render/color.h" struct wlr_color_transform *wlr_color_transform_init_linear_to_icc( const void *data, size_t size) { @@ -7,3 +8,17 @@ struct wlr_color_transform *wlr_color_transform_init_linear_to_icc( "LCMS2 is compile-time disabled"); return NULL; } + +struct wlr_color_transform_lcms2 *color_transform_lcms2_from_base( + struct wlr_color_transform *tr) { + abort(); // unreachable +} + +void color_transform_lcms2_finish(struct wlr_color_transform_lcms2 *tr) { + abort(); // unreachable +} + +void color_transform_lcms2_eval(struct wlr_color_transform_lcms2 *tr, + float out[static 3], const float in[static 3]) { + abort(); // unreachable +} diff --git a/render/color_lcms2.c b/render/color_lcms2.c index 88df46087..39e16cdd7 100644 --- a/render/color_lcms2.c +++ b/render/color_lcms2.c @@ -1,9 +1,17 @@ +#include #include #include #include #include #include "render/color.h" +struct wlr_color_transform_lcms2 { + struct wlr_color_transform base; + + cmsContext ctx; + cmsHTRANSFORM lcms; +}; + static const cmsCIExyY srgb_whitepoint = { 0.3127, 0.3291, 1 }; static const cmsCIExyYTRIPLE srgb_primaries = { @@ -18,7 +26,7 @@ static void handle_lcms_error(cmsContext ctx, cmsUInt32Number code, const char * struct wlr_color_transform *wlr_color_transform_init_linear_to_icc( const void *data, size_t size) { - struct wlr_color_transform_lut3d *tx = NULL; + struct wlr_color_transform_lcms2 *tx = NULL; cmsContext ctx = cmsCreateContext(NULL, NULL); if (ctx == NULL) { @@ -31,18 +39,18 @@ struct wlr_color_transform *wlr_color_transform_init_linear_to_icc( cmsHPROFILE icc_profile = cmsOpenProfileFromMemTHR(ctx, data, size); if (icc_profile == NULL) { wlr_log(WLR_ERROR, "cmsOpenProfileFromMemTHR failed"); - goto out_ctx; + goto error_ctx; } if (cmsGetDeviceClass(icc_profile) != cmsSigDisplayClass) { wlr_log(WLR_ERROR, "ICC profile must have the Display device class"); - goto out_icc_profile; + goto error_icc_profile; } cmsToneCurve *linear_tone_curve = cmsBuildGamma(ctx, 1); if (linear_tone_curve == NULL) { wlr_log(WLR_ERROR, "cmsBuildGamma failed"); - goto out_icc_profile; + goto error_icc_profile; } cmsToneCurve *linear_tf[] = { @@ -52,66 +60,54 @@ struct wlr_color_transform *wlr_color_transform_init_linear_to_icc( }; cmsHPROFILE srgb_profile = cmsCreateRGBProfileTHR(ctx, &srgb_whitepoint, &srgb_primaries, linear_tf); + cmsFreeToneCurve(linear_tone_curve); if (srgb_profile == NULL) { wlr_log(WLR_ERROR, "cmsCreateRGBProfileTHR failed"); - goto out_linear_tone_curve; + goto error_icc_profile; } cmsHTRANSFORM lcms_tr = cmsCreateTransformTHR(ctx, srgb_profile, TYPE_RGB_FLT, icc_profile, TYPE_RGB_FLT, INTENT_RELATIVE_COLORIMETRIC, 0); + cmsCloseProfile(srgb_profile); + cmsCloseProfile(icc_profile); if (lcms_tr == NULL) { wlr_log(WLR_ERROR, "cmsCreateTransformTHR failed"); - goto out_srgb_profile; - } - - size_t dim_len = 33; - float *lut_3d = calloc(3 * dim_len * dim_len * dim_len, sizeof(float)); - if (lut_3d == NULL) { - wlr_log_errno(WLR_ERROR, "Allocation failed"); - goto out_lcms_tr; - } - - float factor = 1.0f / (dim_len - 1); - for (size_t b_index = 0; b_index < dim_len; b_index++) { - for (size_t g_index = 0; g_index < dim_len; g_index++) { - for (size_t r_index = 0; r_index < dim_len; r_index++) { - float rgb_in[3] = { - r_index * factor, - g_index * factor, - b_index * factor, - }; - float rgb_out[3]; - // TODO: use a single call to cmsDoTransform for the entire calculation? - // this does require allocating an extra temp buffer - cmsDoTransform(lcms_tr, rgb_in, rgb_out, 1); - - size_t offset = 3 * (r_index + dim_len * g_index + dim_len * dim_len * b_index); - // TODO: maybe clamp values to [0.0, 1.0] here? - lut_3d[offset] = rgb_out[0]; - lut_3d[offset + 1] = rgb_out[1]; - lut_3d[offset + 2] = rgb_out[2]; - } - } + goto error_ctx; } tx = calloc(1, sizeof(*tx)); if (!tx) { - goto out_lcms_tr; + cmsDeleteTransform(lcms_tr); + goto error_ctx; } - wlr_color_transform_init(&tx->base, COLOR_TRANSFORM_LUT_3D); - tx->dim_len = dim_len; - tx->lut_3d = lut_3d; + wlr_color_transform_init(&tx->base, COLOR_TRANSFORM_LCMS2); + + tx->ctx = ctx; + tx->lcms = lcms_tr; -out_lcms_tr: - cmsDeleteTransform(lcms_tr); -out_linear_tone_curve: - cmsFreeToneCurve(linear_tone_curve); -out_srgb_profile: - cmsCloseProfile(srgb_profile); -out_icc_profile: - cmsCloseProfile(icc_profile); -out_ctx: - cmsDeleteContext(ctx); return &tx->base; + +error_icc_profile: + cmsCloseProfile(icc_profile); +error_ctx: + cmsDeleteContext(ctx); + return NULL; +} + +void color_transform_lcms2_finish(struct wlr_color_transform_lcms2 *tr) { + cmsDeleteTransform(tr->lcms); + cmsDeleteContext(tr->ctx); +} + +struct wlr_color_transform_lcms2 *color_transform_lcms2_from_base( + struct wlr_color_transform *tr) { + assert(tr->type == COLOR_TRANSFORM_LCMS2); + struct wlr_color_transform_lcms2 *lcms2 = wl_container_of(tr, lcms2, base); + return lcms2; +} + +void color_transform_lcms2_eval(struct wlr_color_transform_lcms2 *tr, + float out[static 3], const float in[static 3]) { + cmsDoTransform(tr->lcms, in, out, 1); } diff --git a/render/vulkan/pass.c b/render/vulkan/pass.c index 3f662b203..2ad6d9086 100644 --- a/render/vulkan/pass.c +++ b/render/vulkan/pass.c @@ -179,11 +179,12 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { .uv_size = { 1, 1 }, }; + struct wlr_vk_color_transform *transform = NULL; size_t dim = 1; - if (pass->color_transform && pass->color_transform->type == COLOR_TRANSFORM_LUT_3D) { - struct wlr_color_transform_lut3d *lut3d = - wlr_color_transform_lut3d_from_base(pass->color_transform); - dim = lut3d->dim_len; + if (pass->color_transform && pass->color_transform->type != COLOR_TRANSFORM_SRGB) { + transform = get_color_transform(pass->color_transform, renderer); + assert(transform); + dim = transform->lut_3d.dim; } struct wlr_vk_frag_output_pcr_data frag_pcr_data = { @@ -204,10 +205,7 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { sizeof(frag_pcr_data), &frag_pcr_data); VkDescriptorSet lut_ds; - if (pass->color_transform && pass->color_transform->type == COLOR_TRANSFORM_LUT_3D) { - struct wlr_vk_color_transform *transform = - get_color_transform(pass->color_transform, renderer); - assert(transform); + if (transform != NULL) { lut_ds = transform->lut_3d.ds; } else { lut_ds = renderer->output_ds_lut3d_dummy; @@ -840,9 +838,9 @@ void vk_color_transform_destroy(struct wlr_addon *addon) { } static bool create_3d_lut_image(struct wlr_vk_renderer *renderer, - const struct wlr_color_transform_lut3d *lut_3d, + struct wlr_color_transform_lcms2 *tr, size_t dim_len, VkImage *image, VkImageView *image_view, - VkDeviceMemory *memory, VkDescriptorSet *ds, + VkDeviceMemory *memory, VkDescriptorSet *ds, struct wlr_vk_descriptor_pool **ds_pool) { VkDevice dev = renderer->dev->dev; VkResult res; @@ -866,7 +864,7 @@ static bool create_3d_lut_image(struct wlr_vk_renderer *renderer, .samples = VK_SAMPLE_COUNT_1_BIT, .sharingMode = VK_SHARING_MODE_EXCLUSIVE, .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, - .extent = (VkExtent3D) { lut_3d->dim_len, lut_3d->dim_len, lut_3d->dim_len }, + .extent = (VkExtent3D) { dim_len, dim_len, dim_len }, .tiling = VK_IMAGE_TILING_OPTIMAL, .usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT, }; @@ -927,7 +925,7 @@ static bool create_3d_lut_image(struct wlr_vk_renderer *renderer, } size_t bytes_per_block = 4 * sizeof(float); - size_t size = lut_3d->dim_len * lut_3d->dim_len * lut_3d->dim_len * bytes_per_block; + size_t size = dim_len * dim_len * dim_len * bytes_per_block; struct wlr_vk_buffer_span span = vulkan_get_stage_span(renderer, size, bytes_per_block); if (!span.buffer || span.alloc.size != size) { @@ -935,18 +933,26 @@ static bool create_3d_lut_image(struct wlr_vk_renderer *renderer, goto fail_imageview; } - char *map = (char*)span.buffer->cpu_mapping + span.alloc.start; - float *dst = (float*)map; - size_t dim_len = lut_3d->dim_len; + float sample_range = 1.0f / (dim_len - 1); + char *map = (char *)span.buffer->cpu_mapping + span.alloc.start; + float *dst = (float *)map; for (size_t b_index = 0; b_index < dim_len; b_index++) { for (size_t g_index = 0; g_index < dim_len; g_index++) { for (size_t r_index = 0; r_index < dim_len; r_index++) { size_t sample_index = r_index + dim_len * g_index + dim_len * dim_len * b_index; - size_t src_offset = 3 * sample_index; size_t dst_offset = 4 * sample_index; - dst[dst_offset] = lut_3d->lut_3d[src_offset]; - dst[dst_offset + 1] = lut_3d->lut_3d[src_offset + 1]; - dst[dst_offset + 2] = lut_3d->lut_3d[src_offset + 2]; + + float rgb_in[3] = { + r_index * sample_range, + g_index * sample_range, + b_index * sample_range, + }; + float rgb_out[3]; + color_transform_lcms2_eval(tr, rgb_out, rgb_in); + + dst[dst_offset] = rgb_out[0]; + dst[dst_offset + 1] = rgb_out[1]; + dst[dst_offset + 2] = rgb_out[2]; dst[dst_offset + 3] = 1.0; } } @@ -959,9 +965,9 @@ static bool create_3d_lut_image(struct wlr_vk_renderer *renderer, VK_ACCESS_TRANSFER_WRITE_BIT); VkBufferImageCopy copy = { .bufferOffset = span.alloc.start, - .imageExtent.width = lut_3d->dim_len, - .imageExtent.height = lut_3d->dim_len, - .imageExtent.depth = lut_3d->dim_len, + .imageExtent.width = dim_len, + .imageExtent.height = dim_len, + .imageExtent.depth = dim_len, .imageSubresource.layerCount = 1, .imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, }; @@ -1012,9 +1018,11 @@ static struct wlr_vk_color_transform *vk_color_transform_create( return NULL; } - if (transform->type == COLOR_TRANSFORM_LUT_3D) { + if (transform->type == COLOR_TRANSFORM_LCMS2) { + vk_transform->lut_3d.dim = 33; if (!create_3d_lut_image(renderer, - wlr_color_transform_lut3d_from_base(transform), + color_transform_lcms2_from_base(transform), + vk_transform->lut_3d.dim, &vk_transform->lut_3d.image, &vk_transform->lut_3d.image_view, &vk_transform->lut_3d.memory, From 74217a4d9341db0b214be4a8e65c4695f289d668 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sat, 24 May 2025 11:19:32 +0200 Subject: [PATCH 029/311] render/color: introduce COLOR_TRANSFORM_LUT_3X1D This will be useful to apply LUTs applied via wlr_gamma_control_v1, and to add wlr_color_transform support to wlr_output. --- include/render/color.h | 30 ++++++++++++++++++++ include/wlr/render/color.h | 8 ++++++ render/color.c | 57 ++++++++++++++++++++++++++++++++++++++ render/vulkan/pass.c | 26 +++++++++++++---- 4 files changed, 116 insertions(+), 5 deletions(-) diff --git a/include/render/color.h b/include/render/color.h index a3ab73d83..a797881bf 100644 --- a/include/render/color.h +++ b/include/render/color.h @@ -8,6 +8,7 @@ enum wlr_color_transform_type { COLOR_TRANSFORM_SRGB, COLOR_TRANSFORM_LCMS2, + COLOR_TRANSFORM_LUT_3X1D, }; struct wlr_color_transform { @@ -20,6 +21,21 @@ struct wlr_color_transform { void wlr_color_transform_init(struct wlr_color_transform *tr, enum wlr_color_transform_type type); +/** + * The formula is approximated via three 1D look-up tables. The flat lut_3x1d + * array has a length of 3 * dim. + * + * The offset of a color value for a given channel and color index is: + * + * offset = channel_index * dim + color_index + */ +struct wlr_color_transform_lut_3x1d { + struct wlr_color_transform base; + + uint16_t *lut_3x1d; + size_t dim; +}; + /** * Get a struct wlr_color_transform_lcms2 from a generic struct wlr_color_transform. * Asserts that the base type is COLOR_TRANSFORM_LCMS2. @@ -35,6 +51,20 @@ void color_transform_lcms2_finish(struct wlr_color_transform_lcms2 *tr); void color_transform_lcms2_eval(struct wlr_color_transform_lcms2 *tr, float out[static 3], const float in[static 3]); +/** + * Get a struct wlr_color_transform_lut_3x1d from a generic + * struct wlr_color_transform. Asserts that the base type is + * COLOR_TRANSFORM_LUT_3X1D. + */ +struct wlr_color_transform_lut_3x1d *color_transform_lut_3x1d_from_base( + struct wlr_color_transform *tr); + +/** + * Evaluate a 3x1D LUT color transform for a given RGB triplet. + */ +void color_transform_lut_3x1d_eval(struct wlr_color_transform_lut_3x1d *tr, + float out[static 3], const float in[static 3]); + /** * Obtain primaries values from a well-known primaries name. */ diff --git a/include/wlr/render/color.h b/include/wlr/render/color.h index dc6f4097f..c2cbee265 100644 --- a/include/wlr/render/color.h +++ b/include/wlr/render/color.h @@ -10,6 +10,7 @@ #define WLR_RENDER_COLOR_H #include +#include #include /** @@ -78,6 +79,13 @@ struct wlr_color_transform *wlr_color_transform_init_linear_to_icc( */ struct wlr_color_transform *wlr_color_transform_init_srgb(void); +/** + * Initialize a color transformation to apply three 1D look-up tables. dim + * is the number of elements in each individual LUT. Returns NULL on failure. + */ +struct wlr_color_transform *wlr_color_transform_init_lut_3x1d(size_t dim, + const uint16_t *r, const uint16_t *g, const uint16_t *b); + /** * Increase the reference count of the color transform by 1. */ diff --git a/render/color.c b/render/color.c index 1c8365013..2bd8d65ec 100644 --- a/render/color.c +++ b/render/color.c @@ -38,6 +38,28 @@ struct wlr_color_transform *wlr_color_transform_init_srgb(void) { return tx; } +struct wlr_color_transform *wlr_color_transform_init_lut_3x1d(size_t dim, + const uint16_t *r, const uint16_t *g, const uint16_t *b) { + uint16_t *lut_3x1d = malloc(3 * dim * sizeof(lut_3x1d[0])); + if (lut_3x1d == NULL) { + return NULL; + } + + memcpy(&lut_3x1d[0 * dim], r, dim * sizeof(lut_3x1d[0])); + memcpy(&lut_3x1d[1 * dim], g, dim * sizeof(lut_3x1d[0])); + memcpy(&lut_3x1d[2 * dim], b, dim * sizeof(lut_3x1d[0])); + + struct wlr_color_transform_lut_3x1d *tx = calloc(1, sizeof(*tx)); + if (!tx) { + free(lut_3x1d); + return NULL; + } + wlr_color_transform_init(&tx->base, COLOR_TRANSFORM_LUT_3X1D); + tx->lut_3x1d = lut_3x1d; + tx->dim = dim; + return &tx->base; +} + static void color_transform_destroy(struct wlr_color_transform *tr) { switch (tr->type) { case COLOR_TRANSFORM_SRGB: @@ -45,6 +67,10 @@ static void color_transform_destroy(struct wlr_color_transform *tr) { case COLOR_TRANSFORM_LCMS2: color_transform_lcms2_finish(color_transform_lcms2_from_base(tr)); break; + case COLOR_TRANSFORM_LUT_3X1D:; + struct wlr_color_transform_lut_3x1d *lut_3x1d = color_transform_lut_3x1d_from_base(tr); + free(lut_3x1d->lut_3x1d); + break; } wlr_addon_set_finish(&tr->addons); free(tr); @@ -66,6 +92,37 @@ void wlr_color_transform_unref(struct wlr_color_transform *tr) { } } +struct wlr_color_transform_lut_3x1d *color_transform_lut_3x1d_from_base( + struct wlr_color_transform *tr) { + assert(tr->type == COLOR_TRANSFORM_LUT_3X1D); + struct wlr_color_transform_lut_3x1d *lut_3x1d = wl_container_of(tr, lut_3x1d, base); + return lut_3x1d; +} + +static float lut_1d_get(const uint16_t *lut, size_t len, size_t i) { + if (i > len) { + i = len - 1; + } + return (float) lut[i] / UINT16_MAX; +} + +static float lut_1d_eval(const uint16_t *lut, size_t len, float x) { + double pos = x * (len - 1); + double int_part; + double frac_part = modf(pos, &int_part); + size_t i = (size_t) int_part; + double a = lut_1d_get(lut, len, i); + double b = lut_1d_get(lut, len, i + 1); + return a * (1 - frac_part) + b * frac_part; +} + +void color_transform_lut_3x1d_eval(struct wlr_color_transform_lut_3x1d *tr, + float out[static 3], const float in[static 3]) { + for (size_t i = 0; i < 3; i++) { + out[i] = lut_1d_eval(&tr->lut_3x1d[tr->dim * i], tr->dim, in[i]); + } +} + void wlr_color_primaries_from_named(struct wlr_color_primaries *out, enum wlr_color_named_primaries named) { switch (named) { diff --git a/render/vulkan/pass.c b/render/vulkan/pass.c index 2ad6d9086..529743305 100644 --- a/render/vulkan/pass.c +++ b/render/vulkan/pass.c @@ -838,7 +838,7 @@ void vk_color_transform_destroy(struct wlr_addon *addon) { } static bool create_3d_lut_image(struct wlr_vk_renderer *renderer, - struct wlr_color_transform_lcms2 *tr, size_t dim_len, + struct wlr_color_transform *tr, size_t dim_len, VkImage *image, VkImageView *image_view, VkDeviceMemory *memory, VkDescriptorSet *ds, struct wlr_vk_descriptor_pool **ds_pool) { @@ -851,6 +851,19 @@ static bool create_3d_lut_image(struct wlr_vk_renderer *renderer, *ds = VK_NULL_HANDLE; *ds_pool = NULL; + struct wlr_color_transform_lcms2 *tr_lcms2 = NULL; + struct wlr_color_transform_lut_3x1d *tr_lut_3x1d = NULL; + switch (tr->type) { + case COLOR_TRANSFORM_SRGB: + abort(); // unreachable + case COLOR_TRANSFORM_LCMS2: + tr_lcms2 = color_transform_lcms2_from_base(tr); + break; + case COLOR_TRANSFORM_LUT_3X1D: + tr_lut_3x1d = color_transform_lut_3x1d_from_base(tr); + break; + } + // R32G32B32 is not a required Vulkan format // TODO: use it when available VkFormat format = VK_FORMAT_R32G32B32A32_SFLOAT; @@ -948,7 +961,11 @@ static bool create_3d_lut_image(struct wlr_vk_renderer *renderer, b_index * sample_range, }; float rgb_out[3]; - color_transform_lcms2_eval(tr, rgb_out, rgb_in); + if (tr_lcms2 != NULL) { + color_transform_lcms2_eval(tr_lcms2, rgb_out, rgb_in); + } else { + color_transform_lut_3x1d_eval(tr_lut_3x1d, rgb_out, rgb_in); + } dst[dst_offset] = rgb_out[0]; dst[dst_offset + 1] = rgb_out[1]; @@ -1018,10 +1035,9 @@ static struct wlr_vk_color_transform *vk_color_transform_create( return NULL; } - if (transform->type == COLOR_TRANSFORM_LCMS2) { + if (transform->type != COLOR_TRANSFORM_SRGB) { vk_transform->lut_3d.dim = 33; - if (!create_3d_lut_image(renderer, - color_transform_lcms2_from_base(transform), + if (!create_3d_lut_image(renderer, transform, vk_transform->lut_3d.dim, &vk_transform->lut_3d.image, &vk_transform->lut_3d.image_view, From 97f6946c8d1603dc6faa8689bd7e63aa2cea4a36 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sat, 24 Aug 2024 11:14:54 +0200 Subject: [PATCH 030/311] output: add color transform to state Color transforms are better suited than raw gamma tables, because: - They don't need to get copied around: they are ref'counted. - They can represent more color operations (will be useful for the upcoming KMS color pipeline API, and for the Wayland color management protocol). --- include/wlr/types/wlr_output.h | 10 ++++++++++ types/output/output.c | 4 ++++ types/output/state.c | 20 +++++++++++++++++++- 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/include/wlr/types/wlr_output.h b/include/wlr/types/wlr_output.h index 31b757062..6473ece4f 100644 --- a/include/wlr/types/wlr_output.h +++ b/include/wlr/types/wlr_output.h @@ -74,6 +74,7 @@ enum wlr_output_state_field { WLR_OUTPUT_STATE_LAYERS = 1 << 10, WLR_OUTPUT_STATE_WAIT_TIMELINE = 1 << 11, WLR_OUTPUT_STATE_SIGNAL_TIMELINE = 1 << 12, + WLR_OUTPUT_STATE_COLOR_TRANSFORM = 1 << 13, }; enum wlr_output_state_mode_type { @@ -132,6 +133,8 @@ struct wlr_output_state { uint64_t wait_point; struct wlr_drm_syncobj_timeline *signal_timeline; uint64_t signal_point; + + struct wlr_color_transform *color_transform; }; struct wlr_output_impl; @@ -586,6 +589,13 @@ void wlr_output_state_set_wait_timeline(struct wlr_output_state *state, */ void wlr_output_state_set_signal_timeline(struct wlr_output_state *state, struct wlr_drm_syncobj_timeline *timeline, uint64_t dst_point); +/** + * Set the color transform for an output. + * + * The color transform is applied after blending output layers. + */ +void wlr_output_state_set_color_transform(struct wlr_output_state *state, + struct wlr_color_transform *tr); /** * Copies the output state from src to dst. It is safe to then diff --git a/types/output/output.c b/types/output/output.c index 55f8a7645..9e63ec76e 100644 --- a/types/output/output.c +++ b/types/output/output.c @@ -651,6 +651,10 @@ static bool output_basic_test(struct wlr_output *output, wlr_log(WLR_DEBUG, "Tried to set the subpixel layout on a disabled output"); return false; } + if (!enabled && state->committed & WLR_OUTPUT_STATE_COLOR_TRANSFORM) { + wlr_log(WLR_DEBUG, "Tried to set a color transform on a disabled output"); + return false; + } if (state->committed & WLR_OUTPUT_STATE_LAYERS) { if (state->layers_len != (size_t)wl_list_length(&output->layers)) { diff --git a/types/output/state.c b/types/output/state.c index 72849c027..1a7b00508 100644 --- a/types/output/state.c +++ b/types/output/state.c @@ -1,6 +1,7 @@ #include #include #include +#include #include #include "types/wlr_output.h" @@ -133,6 +134,17 @@ void wlr_output_state_set_signal_timeline(struct wlr_output_state *state, state->signal_point = dst_point; } +void wlr_output_state_set_color_transform(struct wlr_output_state *state, + struct wlr_color_transform *tr) { + state->committed |= WLR_OUTPUT_STATE_COLOR_TRANSFORM; + wlr_color_transform_unref(state->color_transform); + if (tr) { + state->color_transform = wlr_color_transform_ref(tr); + } else { + state->color_transform = NULL; + } +} + bool wlr_output_state_copy(struct wlr_output_state *dst, const struct wlr_output_state *src) { struct wlr_output_state copy = *src; @@ -140,7 +152,8 @@ bool wlr_output_state_copy(struct wlr_output_state *dst, WLR_OUTPUT_STATE_DAMAGE | WLR_OUTPUT_STATE_GAMMA_LUT | WLR_OUTPUT_STATE_WAIT_TIMELINE | - WLR_OUTPUT_STATE_SIGNAL_TIMELINE); + WLR_OUTPUT_STATE_SIGNAL_TIMELINE | + WLR_OUTPUT_STATE_COLOR_TRANSFORM); copy.buffer = NULL; copy.buffer_src_box = (struct wlr_fbox){0}; copy.buffer_dst_box = (struct wlr_box){0}; @@ -149,6 +162,7 @@ bool wlr_output_state_copy(struct wlr_output_state *dst, copy.gamma_lut_size = 0; copy.wait_timeline = NULL; copy.signal_timeline = NULL; + copy.color_transform = NULL; if (src->committed & WLR_OUTPUT_STATE_BUFFER) { wlr_output_state_set_buffer(©, src->buffer); @@ -178,6 +192,10 @@ bool wlr_output_state_copy(struct wlr_output_state *dst, src->signal_point); } + if (src->committed & WLR_OUTPUT_STATE_COLOR_TRANSFORM) { + wlr_output_state_set_color_transform(©, src->color_transform); + } + wlr_output_state_finish(dst); *dst = copy; return true; From f10dd1da1c7b58bd1dde79e18c70109e3f90c3d4 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sat, 24 May 2025 13:40:05 +0200 Subject: [PATCH 031/311] backend/drm: add support for color transforms --- backend/drm/atomic.c | 19 +++++++++++++------ backend/drm/drm.c | 12 ++++++++++-- backend/drm/legacy.c | 15 ++++++++++++--- 3 files changed, 35 insertions(+), 11 deletions(-) diff --git a/backend/drm/atomic.c b/backend/drm/atomic.c index 037c3c23a..cfef2ff36 100644 --- a/backend/drm/atomic.c +++ b/backend/drm/atomic.c @@ -10,6 +10,7 @@ #include "backend/drm/fb.h" #include "backend/drm/iface.h" #include "backend/drm/util.h" +#include "render/color.h" static char *atomic_commit_flags_str(uint32_t flags) { const char *const l[] = { @@ -251,19 +252,25 @@ bool drm_atomic_connector_prepare(struct wlr_drm_connector_state *state, bool mo } uint32_t gamma_lut = crtc->gamma_lut; - if (state->base->committed & WLR_OUTPUT_STATE_GAMMA_LUT) { + if (state->base->committed & WLR_OUTPUT_STATE_COLOR_TRANSFORM) { + size_t dim = 0; + uint16_t *lut = NULL; + if (state->base->color_transform != NULL) { + struct wlr_color_transform_lut_3x1d *tr = + color_transform_lut_3x1d_from_base(state->base->color_transform); + dim = tr->dim; + lut = tr->lut_3x1d; + } + // Fallback to legacy gamma interface when gamma properties are not // available (can happen on older Intel GPUs that support gamma but not // degamma). if (crtc->props.gamma_lut == 0) { - if (!drm_legacy_crtc_set_gamma(drm, crtc, - state->base->gamma_lut_size, - state->base->gamma_lut)) { + if (!drm_legacy_crtc_set_gamma(drm, crtc, dim, lut)) { return false; } } else { - if (!create_gamma_lut_blob(drm, state->base->gamma_lut_size, - state->base->gamma_lut, &gamma_lut)) { + if (!create_gamma_lut_blob(drm, dim, lut, &gamma_lut)) { return false; } } diff --git a/backend/drm/drm.c b/backend/drm/drm.c index e9e4c6db2..653497813 100644 --- a/backend/drm/drm.c +++ b/backend/drm/drm.c @@ -24,6 +24,7 @@ #include "backend/drm/fb.h" #include "backend/drm/iface.h" #include "backend/drm/util.h" +#include "render/color.h" #include "types/wlr_output.h" #include "util/env.h" #include "config.h" @@ -37,11 +38,11 @@ static const uint32_t COMMIT_OUTPUT_STATE = WLR_OUTPUT_STATE_BUFFER | WLR_OUTPUT_STATE_MODE | WLR_OUTPUT_STATE_ENABLED | - WLR_OUTPUT_STATE_GAMMA_LUT | WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED | WLR_OUTPUT_STATE_LAYERS | WLR_OUTPUT_STATE_WAIT_TIMELINE | - WLR_OUTPUT_STATE_SIGNAL_TIMELINE; + WLR_OUTPUT_STATE_SIGNAL_TIMELINE | + WLR_OUTPUT_STATE_COLOR_TRANSFORM; static const uint32_t SUPPORTED_OUTPUT_STATE = WLR_OUTPUT_STATE_BACKEND_OPTIONAL | COMMIT_OUTPUT_STATE; @@ -856,6 +857,13 @@ static bool drm_connector_prepare(struct wlr_drm_connector_state *conn_state, bo } } + if ((state->committed & WLR_OUTPUT_STATE_COLOR_TRANSFORM) && state->color_transform != NULL && + state->color_transform->type != COLOR_TRANSFORM_LUT_3X1D) { + wlr_drm_conn_log(conn, WLR_DEBUG, + "Only 3x1D LUT color transforms are supported"); + return false; + } + if (test_only && conn->backend->mgpu_renderer.wlr_rend) { // If we're running as a secondary GPU, we can't perform an atomic // commit without blitting a buffer. diff --git a/backend/drm/legacy.c b/backend/drm/legacy.c index d77c9d342..223852ec1 100644 --- a/backend/drm/legacy.c +++ b/backend/drm/legacy.c @@ -7,6 +7,7 @@ #include "backend/drm/fb.h" #include "backend/drm/iface.h" #include "backend/drm/util.h" +#include "render/color.h" #include "types/wlr_output.h" static bool legacy_fb_props_match(struct wlr_drm_fb *fb1, @@ -124,9 +125,17 @@ static bool legacy_crtc_commit(const struct wlr_drm_connector_state *state, } } - if (state->base->committed & WLR_OUTPUT_STATE_GAMMA_LUT) { - if (!drm_legacy_crtc_set_gamma(drm, crtc, - state->base->gamma_lut_size, state->base->gamma_lut)) { + if (state->base->committed & WLR_OUTPUT_STATE_COLOR_TRANSFORM) { + size_t dim = 0; + uint16_t *lut = NULL; + if (state->base->color_transform != NULL) { + struct wlr_color_transform_lut_3x1d *tr = + color_transform_lut_3x1d_from_base(state->base->color_transform); + dim = tr->dim; + lut = tr->lut_3x1d; + } + + if (!drm_legacy_crtc_set_gamma(drm, crtc, dim, lut)) { return false; } } From bfcb4211f629be19ba022c1813f2ac381c9f3459 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sat, 24 May 2025 13:48:55 +0200 Subject: [PATCH 032/311] wlr_gamma_control_v1: use color transforms --- types/wlr_gamma_control_v1.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/types/wlr_gamma_control_v1.c b/types/wlr_gamma_control_v1.c index 087281423..207d1977f 100644 --- a/types/wlr_gamma_control_v1.c +++ b/types/wlr_gamma_control_v1.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -263,15 +264,20 @@ struct wlr_gamma_control_v1 *wlr_gamma_control_manager_v1_get_control( bool wlr_gamma_control_v1_apply(struct wlr_gamma_control_v1 *gamma_control, struct wlr_output_state *output_state) { - if (gamma_control == NULL || gamma_control->table == NULL) { - return wlr_output_state_set_gamma_lut(output_state, 0, NULL, NULL, NULL); + struct wlr_color_transform *tr = NULL; + if (gamma_control != NULL && gamma_control->table != NULL) { + const uint16_t *r = gamma_control->table; + const uint16_t *g = gamma_control->table + gamma_control->ramp_size; + const uint16_t *b = gamma_control->table + 2 * gamma_control->ramp_size; + + tr = wlr_color_transform_init_lut_3x1d(gamma_control->ramp_size, r, g, b); + if (tr == NULL) { + return false; + } } - const uint16_t *r = gamma_control->table; - const uint16_t *g = gamma_control->table + gamma_control->ramp_size; - const uint16_t *b = gamma_control->table + 2 * gamma_control->ramp_size; - return wlr_output_state_set_gamma_lut(output_state, - gamma_control->ramp_size, r, g, b); + wlr_output_state_set_color_transform(output_state, tr); + return true; } void wlr_gamma_control_v1_send_failed_and_destroy(struct wlr_gamma_control_v1 *gamma_control) { From a30c1021638b08654c3b6528a25da4b50ad6d6bf Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sat, 24 Aug 2024 11:16:22 +0200 Subject: [PATCH 033/311] output: drop gamma LUT from state This has been superseded by color transforms. --- include/wlr/types/wlr_output.h | 27 ++++++----------------- types/output/output.c | 4 ---- types/output/state.c | 39 ---------------------------------- 3 files changed, 6 insertions(+), 64 deletions(-) diff --git a/include/wlr/types/wlr_output.h b/include/wlr/types/wlr_output.h index 6473ece4f..dfa5ddb01 100644 --- a/include/wlr/types/wlr_output.h +++ b/include/wlr/types/wlr_output.h @@ -68,13 +68,12 @@ enum wlr_output_state_field { WLR_OUTPUT_STATE_SCALE = 1 << 4, WLR_OUTPUT_STATE_TRANSFORM = 1 << 5, WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED = 1 << 6, - WLR_OUTPUT_STATE_GAMMA_LUT = 1 << 7, - WLR_OUTPUT_STATE_RENDER_FORMAT = 1 << 8, - WLR_OUTPUT_STATE_SUBPIXEL = 1 << 9, - WLR_OUTPUT_STATE_LAYERS = 1 << 10, - WLR_OUTPUT_STATE_WAIT_TIMELINE = 1 << 11, - WLR_OUTPUT_STATE_SIGNAL_TIMELINE = 1 << 12, - WLR_OUTPUT_STATE_COLOR_TRANSFORM = 1 << 13, + WLR_OUTPUT_STATE_RENDER_FORMAT = 1 << 7, + WLR_OUTPUT_STATE_SUBPIXEL = 1 << 8, + WLR_OUTPUT_STATE_LAYERS = 1 << 9, + WLR_OUTPUT_STATE_WAIT_TIMELINE = 1 << 10, + WLR_OUTPUT_STATE_SIGNAL_TIMELINE = 1 << 11, + WLR_OUTPUT_STATE_COLOR_TRANSFORM = 1 << 12, }; enum wlr_output_state_mode_type { @@ -123,9 +122,6 @@ struct wlr_output_state { int32_t refresh; // mHz, may be zero } custom_mode; - uint16_t *gamma_lut; - size_t gamma_lut_size; - struct wlr_output_layer_state *layers; size_t layers_len; @@ -530,17 +526,6 @@ void wlr_output_state_set_subpixel(struct wlr_output_state *state, */ void wlr_output_state_set_buffer(struct wlr_output_state *state, struct wlr_buffer *buffer); -/** - * Sets the gamma table for an output. `r`, `g` and `b` are gamma ramps for - * red, green and blue. `size` is the length of the ramps and must not exceed - * the value returned by wlr_output_get_gamma_size(). - * - * Providing zero-sized ramps resets the gamma table. - * - * This state will be applied once wlr_output_commit_state() is called. - */ -bool wlr_output_state_set_gamma_lut(struct wlr_output_state *state, - size_t ramp_size, const uint16_t *r, const uint16_t *g, const uint16_t *b); /** * Sets the damage region for an output. This is used as a hint to the backend * and can be used to reduce power consumption or increase performance on some diff --git a/types/output/output.c b/types/output/output.c index 9e63ec76e..1e60bc669 100644 --- a/types/output/output.c +++ b/types/output/output.c @@ -643,10 +643,6 @@ static bool output_basic_test(struct wlr_output *output, wlr_log(WLR_DEBUG, "Tried to set format for a disabled output"); return false; } - if (!enabled && state->committed & WLR_OUTPUT_STATE_GAMMA_LUT) { - wlr_log(WLR_DEBUG, "Tried to set the gamma lut on a disabled output"); - return false; - } if (!enabled && state->committed & WLR_OUTPUT_STATE_SUBPIXEL) { wlr_log(WLR_DEBUG, "Tried to set the subpixel layout on a disabled output"); return false; diff --git a/types/output/state.c b/types/output/state.c index 1a7b00508..379fd1c51 100644 --- a/types/output/state.c +++ b/types/output/state.c @@ -17,7 +17,6 @@ void wlr_output_state_finish(struct wlr_output_state *state) { // reads it after output_state_finish(). state->buffer = NULL; pixman_region32_fini(&state->damage); - free(state->gamma_lut); wlr_drm_syncobj_timeline_unref(state->wait_timeline); wlr_drm_syncobj_timeline_unref(state->signal_timeline); } @@ -89,28 +88,6 @@ void wlr_output_state_set_damage(struct wlr_output_state *state, pixman_region32_copy(&state->damage, damage); } -bool wlr_output_state_set_gamma_lut(struct wlr_output_state *state, - size_t ramp_size, const uint16_t *r, const uint16_t *g, const uint16_t *b) { - uint16_t *gamma_lut = NULL; - if (ramp_size > 0) { - gamma_lut = realloc(state->gamma_lut, 3 * ramp_size * sizeof(uint16_t)); - if (gamma_lut == NULL) { - wlr_log_errno(WLR_ERROR, "Allocation failed"); - return false; - } - memcpy(gamma_lut, r, ramp_size * sizeof(uint16_t)); - memcpy(gamma_lut + ramp_size, g, ramp_size * sizeof(uint16_t)); - memcpy(gamma_lut + 2 * ramp_size, b, ramp_size * sizeof(uint16_t)); - } else { - free(state->gamma_lut); - } - - state->committed |= WLR_OUTPUT_STATE_GAMMA_LUT; - state->gamma_lut_size = ramp_size; - state->gamma_lut = gamma_lut; - return true; -} - void wlr_output_state_set_layers(struct wlr_output_state *state, struct wlr_output_layer_state *layers, size_t layers_len) { state->committed |= WLR_OUTPUT_STATE_LAYERS; @@ -150,7 +127,6 @@ bool wlr_output_state_copy(struct wlr_output_state *dst, struct wlr_output_state copy = *src; copy.committed &= ~(WLR_OUTPUT_STATE_BUFFER | WLR_OUTPUT_STATE_DAMAGE | - WLR_OUTPUT_STATE_GAMMA_LUT | WLR_OUTPUT_STATE_WAIT_TIMELINE | WLR_OUTPUT_STATE_SIGNAL_TIMELINE | WLR_OUTPUT_STATE_COLOR_TRANSFORM); @@ -158,8 +134,6 @@ bool wlr_output_state_copy(struct wlr_output_state *dst, copy.buffer_src_box = (struct wlr_fbox){0}; copy.buffer_dst_box = (struct wlr_box){0}; pixman_region32_init(©.damage); - copy.gamma_lut = NULL; - copy.gamma_lut_size = 0; copy.wait_timeline = NULL; copy.signal_timeline = NULL; copy.color_transform = NULL; @@ -174,15 +148,6 @@ bool wlr_output_state_copy(struct wlr_output_state *dst, wlr_output_state_set_damage(©, &src->damage); } - if (src->committed & WLR_OUTPUT_STATE_GAMMA_LUT) { - const uint16_t *r = src->gamma_lut; - const uint16_t *g = src->gamma_lut + src->gamma_lut_size; - const uint16_t *b = src->gamma_lut + 2 * src->gamma_lut_size; - if (!wlr_output_state_set_gamma_lut(©, src->gamma_lut_size, r, g, b)) { - goto err; - } - } - if (src->committed & WLR_OUTPUT_STATE_WAIT_TIMELINE) { wlr_output_state_set_wait_timeline(©, src->wait_timeline, src->wait_point); @@ -199,8 +164,4 @@ bool wlr_output_state_copy(struct wlr_output_state *dst, wlr_output_state_finish(dst); *dst = copy; return true; - -err: - wlr_output_state_finish(©); - return false; } From 2ea0e386c4cbcb3147eda56f2db02d00d2a3ea66 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Mon, 27 Jan 2025 18:04:53 +0100 Subject: [PATCH 034/311] render/vulkan: add color transformation matrix --- include/render/vulkan.h | 1 + render/vulkan/pass.c | 7 ++++++- render/vulkan/shaders/output.frag | 7 +++++-- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/include/render/vulkan.h b/include/render/vulkan.h index 78109356f..1db42fe4c 100644 --- a/include/render/vulkan.h +++ b/include/render/vulkan.h @@ -335,6 +335,7 @@ struct wlr_vk_vert_pcr_data { }; struct wlr_vk_frag_output_pcr_data { + float matrix[4][4]; // only a 3x3 subset is used float lut_3d_offset; float lut_3d_scale; }; diff --git a/render/vulkan/pass.c b/render/vulkan/pass.c index 529743305..32984e3c7 100644 --- a/render/vulkan/pass.c +++ b/render/vulkan/pass.c @@ -178,6 +178,7 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { .uv_off = { 0, 0 }, .uv_size = { 1, 1 }, }; + mat3_to_mat4(final_matrix, vert_pcr_data.mat4); struct wlr_vk_color_transform *transform = NULL; size_t dim = 1; @@ -188,10 +189,14 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { } struct wlr_vk_frag_output_pcr_data frag_pcr_data = { + .matrix = { + {1, 0, 0}, + {0, 1, 0}, + {0, 0, 1}, + }, .lut_3d_offset = 0.5f / dim, .lut_3d_scale = (float)(dim - 1) / dim, }; - mat3_to_mat4(final_matrix, vert_pcr_data.mat4); if (pass->color_transform) { bind_pipeline(pass, render_buffer->plain.render_setup->output_pipe_lut3d); diff --git a/render/vulkan/shaders/output.frag b/render/vulkan/shaders/output.frag index e351079da..783817707 100644 --- a/render/vulkan/shaders/output.frag +++ b/render/vulkan/shaders/output.frag @@ -8,8 +8,9 @@ layout(location = 0) in vec2 uv; layout(location = 0) out vec4 out_color; /* struct wlr_vk_frag_output_pcr_data */ -layout(push_constant) uniform UBO { - layout(offset = 80) float lut_3d_offset; +layout(push_constant, row_major) uniform UBO { + layout(offset = 80) mat4 matrix; + float lut_3d_offset; float lut_3d_scale; } data; @@ -43,6 +44,8 @@ void main() { rgb = in_color.rgb / alpha; } + rgb = mat3(data.matrix) * rgb; + if (OUTPUT_TRANSFORM == OUTPUT_TRANSFORM_LUT_3D) { // Apply 3D LUT vec3 pos = data.lut_3d_offset + rgb * data.lut_3d_scale; From 30c6efedf1106ccff326e1e843c21ab53d9c56e8 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Mon, 27 Jan 2025 18:35:01 +0100 Subject: [PATCH 035/311] render/vulkan: use output_pipe_srgb for non-NULL sRGB color transform --- render/vulkan/pass.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/render/vulkan/pass.c b/render/vulkan/pass.c index 32984e3c7..2fddfb008 100644 --- a/render/vulkan/pass.c +++ b/render/vulkan/pass.c @@ -198,7 +198,7 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { .lut_3d_scale = (float)(dim - 1) / dim, }; - if (pass->color_transform) { + if (pass->color_transform && pass->color_transform->type != COLOR_TRANSFORM_SRGB) { bind_pipeline(pass, render_buffer->plain.render_setup->output_pipe_lut3d); } else { bind_pipeline(pass, render_buffer->plain.render_setup->output_pipe_srgb); From 1df2274f6cb52edc122dd9bf56e5f448eca87524 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Tue, 17 Jun 2025 19:35:16 +0200 Subject: [PATCH 036/311] render/vulkan: rename mat3_to_mat4() to encode_proj_matrix() This function is specific to projection matrices. --- render/vulkan/pass.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/render/vulkan/pass.c b/render/vulkan/pass.c index 2fddfb008..83a96686d 100644 --- a/render/vulkan/pass.c +++ b/render/vulkan/pass.c @@ -67,7 +67,7 @@ static float color_to_linear_premult(float non_linear, float alpha) { return (alpha == 0) ? 0 : color_to_linear(non_linear / alpha) * alpha; } -static void mat3_to_mat4(const float mat3[9], float mat4[4][4]) { +static void encode_proj_matrix(const float mat3[9], float mat4[4][4]) { memset(mat4, 0, sizeof(float) * 16); mat4[0][0] = mat3[0]; mat4[0][1] = mat3[1]; @@ -178,7 +178,7 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { .uv_off = { 0, 0 }, .uv_size = { 1, 1 }, }; - mat3_to_mat4(final_matrix, vert_pcr_data.mat4); + encode_proj_matrix(final_matrix, vert_pcr_data.mat4); struct wlr_vk_color_transform *transform = NULL; size_t dim = 1; @@ -644,7 +644,7 @@ static void render_pass_add_rect(struct wlr_render_pass *wlr_pass, .uv_off = { 0, 0 }, .uv_size = { 1, 1 }, }; - mat3_to_mat4(matrix, vert_pcr_data.mat4); + encode_proj_matrix(matrix, vert_pcr_data.mat4); bind_pipeline(pass, pipe->vk); vkCmdPushConstants(cb, pipe->layout->vk, @@ -727,7 +727,7 @@ static void render_pass_add_texture(struct wlr_render_pass *wlr_pass, src_box.height / options->texture->height, }, }; - mat3_to_mat4(matrix, vert_pcr_data.mat4); + encode_proj_matrix(matrix, vert_pcr_data.mat4); struct wlr_vk_render_format_setup *setup = pass->srgb_pathway ? pass->render_buffer->srgb.render_setup : From a5706e2fb925a163d1675114b7e4fa744863310e Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Tue, 17 Jun 2025 19:41:29 +0200 Subject: [PATCH 037/311] render/vulkan: use array declaration in encode_proj_matrix() This makes it more obvious what the final layout of the matrix will be. --- render/vulkan/pass.c | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/render/vulkan/pass.c b/render/vulkan/pass.c index 83a96686d..b44eef0a2 100644 --- a/render/vulkan/pass.c +++ b/render/vulkan/pass.c @@ -68,17 +68,14 @@ static float color_to_linear_premult(float non_linear, float alpha) { } static void encode_proj_matrix(const float mat3[9], float mat4[4][4]) { - memset(mat4, 0, sizeof(float) * 16); - mat4[0][0] = mat3[0]; - mat4[0][1] = mat3[1]; - mat4[0][3] = mat3[2]; + float result[4][4] = { + { mat3[0], mat3[1], 0, mat3[2] }, + { mat3[3], mat3[4], 0, mat3[5] }, + { 0, 0, 1, 0 }, + { 0, 0, 0, 1 }, + }; - mat4[1][0] = mat3[3]; - mat4[1][1] = mat3[4]; - mat4[1][3] = mat3[5]; - - mat4[2][2] = 1.f; - mat4[3][3] = 1.f; + memcpy(mat4, result, sizeof(result)); } static void render_pass_destroy(struct wlr_vk_render_pass *pass) { From f3524de98095a0e595758b27db4c3e6b0d72d7d1 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Wed, 18 Jun 2025 00:07:35 +0200 Subject: [PATCH 038/311] render, render/vulkan: add primaries to wlr_buffer_pass_options --- include/render/vulkan.h | 4 ++++ include/wlr/render/pass.h | 2 ++ render/vulkan/pass.c | 38 +++++++++++++++++++++++++++++++++----- 3 files changed, 39 insertions(+), 5 deletions(-) diff --git a/include/render/vulkan.h b/include/render/vulkan.h index 1db42fe4c..b4a2852af 100644 --- a/include/render/vulkan.h +++ b/include/render/vulkan.h @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -390,6 +391,9 @@ struct wlr_vk_render_pass { bool srgb_pathway; // if false, rendering via intermediate blending buffer struct wlr_color_transform *color_transform; + bool has_primaries; + struct wlr_color_primaries primaries; + struct wlr_drm_syncobj_timeline *signal_timeline; uint64_t signal_point; diff --git a/include/wlr/render/pass.h b/include/wlr/render/pass.h index 2159bc3df..1323a8c9b 100644 --- a/include/wlr/render/pass.h +++ b/include/wlr/render/pass.h @@ -33,6 +33,8 @@ struct wlr_buffer_pass_options { /* Color transform to apply to the output of the render pass, * leave NULL to indicate sRGB/no custom transform */ struct wlr_color_transform *color_transform; + /** Primaries describing the color volume of the destination buffer */ + const struct wlr_color_primaries *primaries; /* Signal a timeline synchronization point when the render pass completes. * diff --git a/render/vulkan/pass.c b/render/vulkan/pass.c index b44eef0a2..9ce77eb08 100644 --- a/render/vulkan/pass.c +++ b/render/vulkan/pass.c @@ -78,6 +78,17 @@ static void encode_proj_matrix(const float mat3[9], float mat4[4][4]) { memcpy(mat4, result, sizeof(result)); } +static void encode_color_matrix(const float mat3[9], float mat4[4][4]) { + float result[4][4] = { + { mat3[0], mat3[1], mat3[2], 0 }, + { mat3[3], mat3[4], mat3[5], 0 }, + { mat3[6], mat3[7], mat3[8], 0 }, + { 0, 0, 0, 0 }, + }; + + memcpy(mat4, result, sizeof(result)); +} + static void render_pass_destroy(struct wlr_vk_render_pass *pass) { struct wlr_vk_render_pass_texture *pass_texture; wl_array_for_each(pass_texture, &pass->textures) { @@ -186,15 +197,28 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { } struct wlr_vk_frag_output_pcr_data frag_pcr_data = { - .matrix = { - {1, 0, 0}, - {0, 1, 0}, - {0, 0, 1}, - }, .lut_3d_offset = 0.5f / dim, .lut_3d_scale = (float)(dim - 1) / dim, }; + float matrix[9]; + if (pass->has_primaries) { + struct wlr_color_primaries srgb; + wlr_color_primaries_from_named(&srgb, WLR_COLOR_NAMED_PRIMARIES_SRGB); + + float srgb_to_xyz[9]; + wlr_color_primaries_to_xyz(&srgb, srgb_to_xyz); + float dst_primaries_to_xyz[9]; + wlr_color_primaries_to_xyz(&pass->primaries, dst_primaries_to_xyz); + float xyz_to_dst_primaries[9]; + matrix_invert(xyz_to_dst_primaries, dst_primaries_to_xyz); + + wlr_matrix_multiply(matrix, srgb_to_xyz, xyz_to_dst_primaries); + } else { + wlr_matrix_identity(matrix); + } + encode_color_matrix(matrix, frag_pcr_data.matrix); + if (pass->color_transform && pass->color_transform->type != COLOR_TRANSFORM_SRGB) { bind_pipeline(pass, render_buffer->plain.render_setup->output_pipe_lut3d); } else { @@ -1106,6 +1130,10 @@ struct wlr_vk_render_pass *vulkan_begin_render_pass(struct wlr_vk_renderer *rend pass->signal_timeline = wlr_drm_syncobj_timeline_ref(options->signal_timeline); pass->signal_point = options->signal_point; } + if (options != NULL && options->primaries != NULL) { + pass->has_primaries = true; + pass->primaries = *options->primaries; + } rect_union_init(&pass->updated_region); From e64de4d55f9c4de2b1d08ba8b41968d0535f6280 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Mon, 27 Jan 2025 20:17:34 +0100 Subject: [PATCH 039/311] output: add color primaries to output state --- include/wlr/types/wlr_output.h | 23 +++++++++++++++++++++++ types/output/output.c | 12 ++++++++++++ types/output/state.c | 31 ++++++++++++++++++++++++++++++- 3 files changed, 65 insertions(+), 1 deletion(-) diff --git a/include/wlr/types/wlr_output.h b/include/wlr/types/wlr_output.h index dfa5ddb01..c94b82116 100644 --- a/include/wlr/types/wlr_output.h +++ b/include/wlr/types/wlr_output.h @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -74,6 +75,7 @@ enum wlr_output_state_field { WLR_OUTPUT_STATE_WAIT_TIMELINE = 1 << 10, WLR_OUTPUT_STATE_SIGNAL_TIMELINE = 1 << 11, WLR_OUTPUT_STATE_COLOR_TRANSFORM = 1 << 12, + WLR_OUTPUT_STATE_IMAGE_DESCRIPTION = 1 << 13, }; enum wlr_output_state_mode_type { @@ -81,6 +83,17 @@ enum wlr_output_state_mode_type { WLR_OUTPUT_STATE_MODE_CUSTOM, }; +/** + * Colorimetric image description. + * + * Carries information about the color encoding used for a struct wlr_buffer. + * + * Supported primaries are advertised in wlr_output.supported_primaries. + */ +struct wlr_output_image_description { + enum wlr_color_named_primaries primaries; +}; + /** * Holds the double-buffered output state. */ @@ -131,6 +144,8 @@ struct wlr_output_state { uint64_t signal_point; struct wlr_color_transform *color_transform; + + struct wlr_output_image_description *image_description; }; struct wlr_output_impl; @@ -166,6 +181,8 @@ struct wlr_output { int32_t width, height; int32_t refresh; // mHz, may be zero + uint32_t supported_primaries; // bitfield of enum wlr_color_named_primaries + bool enabled; float scale; enum wl_output_subpixel subpixel; @@ -582,6 +599,12 @@ void wlr_output_state_set_signal_timeline(struct wlr_output_state *state, void wlr_output_state_set_color_transform(struct wlr_output_state *state, struct wlr_color_transform *tr); +/** + * Set the colorimetry image description. + */ +bool wlr_output_state_set_image_description(struct wlr_output_state *state, + const struct wlr_output_image_description *image_desc); + /** * Copies the output state from src to dst. It is safe to then * wlr_output_state_finish() src and have dst still be valid. diff --git a/types/output/output.c b/types/output/output.c index 1e60bc669..a0a3f9e02 100644 --- a/types/output/output.c +++ b/types/output/output.c @@ -651,6 +651,10 @@ static bool output_basic_test(struct wlr_output *output, wlr_log(WLR_DEBUG, "Tried to set a color transform on a disabled output"); return false; } + if (!enabled && state->committed & WLR_OUTPUT_STATE_IMAGE_DESCRIPTION) { + wlr_log(WLR_DEBUG, "Tried to set the image description on a disabled output"); + return false; + } if (state->committed & WLR_OUTPUT_STATE_LAYERS) { if (state->layers_len != (size_t)wl_list_length(&output->layers)) { @@ -669,6 +673,14 @@ static bool output_basic_test(struct wlr_output *output, return false; } + if ((state->committed & WLR_OUTPUT_STATE_IMAGE_DESCRIPTION) && + state->image_description != NULL) { + if (!(output->supported_primaries & state->image_description->primaries)) { + wlr_log(WLR_DEBUG, "Unsupported image description primaries"); + return false; + } + } + return true; } diff --git a/types/output/state.c b/types/output/state.c index 379fd1c51..a2e8a0042 100644 --- a/types/output/state.c +++ b/types/output/state.c @@ -19,6 +19,7 @@ void wlr_output_state_finish(struct wlr_output_state *state) { pixman_region32_fini(&state->damage); wlr_drm_syncobj_timeline_unref(state->wait_timeline); wlr_drm_syncobj_timeline_unref(state->signal_timeline); + free(state->image_description); } void wlr_output_state_set_enabled(struct wlr_output_state *state, @@ -122,6 +123,23 @@ void wlr_output_state_set_color_transform(struct wlr_output_state *state, } } +bool wlr_output_state_set_image_description(struct wlr_output_state *state, + const struct wlr_output_image_description *image_desc) { + struct wlr_output_image_description *copy = NULL; + if (image_desc != NULL) { + copy = malloc(sizeof(*copy)); + if (copy == NULL) { + return false; + } + *copy = *image_desc; + } + + state->committed |= WLR_OUTPUT_STATE_IMAGE_DESCRIPTION; + free(state->image_description); + state->image_description = copy; + return true; +} + bool wlr_output_state_copy(struct wlr_output_state *dst, const struct wlr_output_state *src) { struct wlr_output_state copy = *src; @@ -129,7 +147,8 @@ bool wlr_output_state_copy(struct wlr_output_state *dst, WLR_OUTPUT_STATE_DAMAGE | WLR_OUTPUT_STATE_WAIT_TIMELINE | WLR_OUTPUT_STATE_SIGNAL_TIMELINE | - WLR_OUTPUT_STATE_COLOR_TRANSFORM); + WLR_OUTPUT_STATE_COLOR_TRANSFORM | + WLR_OUTPUT_STATE_IMAGE_DESCRIPTION); copy.buffer = NULL; copy.buffer_src_box = (struct wlr_fbox){0}; copy.buffer_dst_box = (struct wlr_box){0}; @@ -137,6 +156,7 @@ bool wlr_output_state_copy(struct wlr_output_state *dst, copy.wait_timeline = NULL; copy.signal_timeline = NULL; copy.color_transform = NULL; + copy.image_description = NULL; if (src->committed & WLR_OUTPUT_STATE_BUFFER) { wlr_output_state_set_buffer(©, src->buffer); @@ -160,8 +180,17 @@ bool wlr_output_state_copy(struct wlr_output_state *dst, if (src->committed & WLR_OUTPUT_STATE_COLOR_TRANSFORM) { wlr_output_state_set_color_transform(©, src->color_transform); } + if (src->committed & WLR_OUTPUT_STATE_IMAGE_DESCRIPTION) { + if (!wlr_output_state_set_image_description(©, src->image_description)) { + goto err; + } + } wlr_output_state_finish(dst); *dst = copy; return true; + +err: + wlr_output_state_finish(dst); + return false; } From f024d1b8c8e0e7833ab0ebd239535f586977eba8 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Mon, 27 Jan 2025 20:24:36 +0100 Subject: [PATCH 040/311] backend/drm: add support for color primaries --- backend/drm/atomic.c | 22 ++++++++++++++++++++++ backend/drm/drm.c | 9 ++++++++- backend/drm/meson.build | 1 + backend/drm/properties.c | 1 + backend/drm/util.c | 6 ++++++ include/backend/drm/drm.h | 4 ++++ include/backend/drm/properties.h | 1 + 7 files changed, 43 insertions(+), 1 deletion(-) diff --git a/backend/drm/atomic.c b/backend/drm/atomic.c index cfef2ff36..5d42dad5f 100644 --- a/backend/drm/atomic.c +++ b/backend/drm/atomic.c @@ -178,6 +178,16 @@ bool create_fb_damage_clips_blob(struct wlr_drm_backend *drm, return true; } +static uint64_t convert_primaries_to_colorspace(uint32_t primaries) { + switch (primaries) { + case 0: + return 0; // Default + case WLR_COLOR_NAMED_PRIMARIES_BT2020: + return 9; // BT2020_RGB + } + abort(); // unreachable +} + static uint64_t max_bpc_for_format(uint32_t format) { switch (format) { case DRM_FORMAT_XRGB2101010: @@ -302,11 +312,18 @@ bool drm_atomic_connector_prepare(struct wlr_drm_connector_state *state, bool mo vrr_enabled = state->base->adaptive_sync_enabled; } + uint32_t colorspace = conn->colorspace; + if (state->base->committed & WLR_OUTPUT_STATE_IMAGE_DESCRIPTION) { + colorspace = convert_primaries_to_colorspace( + state->base->image_description ? state->base->image_description->primaries : 0); + } + state->mode_id = mode_id; state->gamma_lut = gamma_lut; state->fb_damage_clips = fb_damage_clips; state->primary_in_fence_fd = in_fence_fd; state->vrr_enabled = vrr_enabled; + state->colorspace = colorspace; return true; } @@ -335,6 +352,8 @@ void drm_atomic_connector_apply_commit(struct wlr_drm_connector_state *state) { state->base->signal_point, state->out_fence_fd); close(state->out_fence_fd); } + + conn->colorspace = state->colorspace; } void drm_atomic_connector_rollback_commit(struct wlr_drm_connector_state *state) { @@ -435,6 +454,9 @@ static void atomic_connector_add(struct atomic *atom, if (modeset && active && conn->props.max_bpc != 0 && conn->max_bpc_bounds[1] != 0) { atomic_add(atom, conn->id, conn->props.max_bpc, pick_max_bpc(conn, state->primary_fb)); } + if (conn->props.colorspace != 0) { + atomic_add(atom, conn->id, conn->props.colorspace, state->colorspace); + } atomic_add(atom, crtc->id, crtc->props.mode_id, state->mode_id); atomic_add(atom, crtc->id, crtc->props.active, active); if (active) { diff --git a/backend/drm/drm.c b/backend/drm/drm.c index 653497813..15cb181cf 100644 --- a/backend/drm/drm.c +++ b/backend/drm/drm.c @@ -42,7 +42,8 @@ static const uint32_t COMMIT_OUTPUT_STATE = WLR_OUTPUT_STATE_LAYERS | WLR_OUTPUT_STATE_WAIT_TIMELINE | WLR_OUTPUT_STATE_SIGNAL_TIMELINE | - WLR_OUTPUT_STATE_COLOR_TRANSFORM; + WLR_OUTPUT_STATE_COLOR_TRANSFORM | + WLR_OUTPUT_STATE_IMAGE_DESCRIPTION; static const uint32_t SUPPORTED_OUTPUT_STATE = WLR_OUTPUT_STATE_BACKEND_OPTIONAL | COMMIT_OUTPUT_STATE; @@ -864,6 +865,12 @@ static bool drm_connector_prepare(struct wlr_drm_connector_state *conn_state, bo return false; } + if ((state->committed & WLR_OUTPUT_STATE_IMAGE_DESCRIPTION) && + conn->backend->iface != &atomic_iface) { + wlr_log(WLR_DEBUG, "Image descriptions are only supported by the atomic interface"); + return false; + } + if (test_only && conn->backend->mgpu_renderer.wlr_rend) { // If we're running as a secondary GPU, we can't perform an atomic // commit without blitting a buffer. diff --git a/backend/drm/meson.build b/backend/drm/meson.build index 666c7cc46..d9e5b77f5 100644 --- a/backend/drm/meson.build +++ b/backend/drm/meson.build @@ -7,6 +7,7 @@ hwdata = dependency( libdisplay_info = dependency( 'libdisplay-info', + version: '>=0.2.0', required: 'drm' in backends, fallback: 'libdisplay-info', not_found_message: 'Required for the DRM backend.', diff --git a/backend/drm/properties.c b/backend/drm/properties.c index 0104304e5..ca48c0d57 100644 --- a/backend/drm/properties.c +++ b/backend/drm/properties.c @@ -22,6 +22,7 @@ struct prop_info { static const struct prop_info connector_info[] = { #define INDEX(name) (offsetof(struct wlr_drm_connector_props, name) / sizeof(uint32_t)) { "CRTC_ID", INDEX(crtc_id) }, + { "Colorspace", INDEX(colorspace) }, { "DPMS", INDEX(dpms) }, { "EDID", INDEX(edid) }, { "PATH", INDEX(path) }, diff --git a/backend/drm/util.c b/backend/drm/util.c index f30898966..365ebbe08 100644 --- a/backend/drm/util.c +++ b/backend/drm/util.c @@ -83,6 +83,12 @@ void parse_edid(struct wlr_drm_connector *conn, size_t len, const uint8_t *data) output->model = di_info_get_model(info); output->serial = di_info_get_serial(info); + const struct di_supported_signal_colorimetry *colorimetry = di_info_get_supported_signal_colorimetry(info); + bool has_bt2020 = colorimetry->bt2020_cycc || colorimetry->bt2020_ycc || colorimetry->bt2020_rgb; + if (conn->props.colorspace != 0 && has_bt2020) { + output->supported_primaries |= WLR_COLOR_NAMED_PRIMARIES_BT2020; + } + di_info_destroy(info); } diff --git a/include/backend/drm/drm.h b/include/backend/drm/drm.h index 9409b7b10..c8f26184b 100644 --- a/include/backend/drm/drm.h +++ b/include/backend/drm/drm.h @@ -158,6 +158,7 @@ struct wlr_drm_connector_state { uint32_t fb_damage_clips; int primary_in_fence_fd, out_fence_fd; bool vrr_enabled; + uint32_t colorspace; }; /** @@ -212,6 +213,9 @@ struct wlr_drm_connector { // Last committed page-flip struct wlr_drm_page_flip *pending_page_flip; + // Atomic modesetting only + uint32_t colorspace; + int32_t refresh; }; diff --git a/include/backend/drm/properties.h b/include/backend/drm/properties.h index 421eb4275..870ea4379 100644 --- a/include/backend/drm/properties.h +++ b/include/backend/drm/properties.h @@ -26,6 +26,7 @@ struct wlr_drm_connector_props { // atomic-modesetting only uint32_t crtc_id; + uint32_t colorspace; }; struct wlr_drm_crtc_props { From 8430a1922d9f8762c8fef9c63f1cb076caa71bc5 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Tue, 28 Jan 2025 10:46:25 +0100 Subject: [PATCH 041/311] render/vulkan: add PQ inverse EOTF to output shader --- include/render/vulkan.h | 4 +++- render/vulkan/renderer.c | 10 ++++++++-- render/vulkan/shaders/output.frag | 16 +++++++++++++++- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/include/render/vulkan.h b/include/render/vulkan.h index b4a2852af..530fa1d27 100644 --- a/include/render/vulkan.h +++ b/include/render/vulkan.h @@ -163,7 +163,8 @@ enum wlr_vk_shader_source { // fragment shader. Must match those in shaders/output.frag enum wlr_vk_output_transform { WLR_VK_OUTPUT_TRANSFORM_INVERSE_SRGB = 0, - WLR_VK_OUTPUT_TRANSFORM_LUT3D = 1, + WLR_VK_OUTPUT_TRANSFORM_INVERSE_ST2084_PQ = 1, + WLR_VK_OUTPUT_TRANSFORM_LUT3D = 2, }; struct wlr_vk_pipeline_key { @@ -193,6 +194,7 @@ struct wlr_vk_render_format_setup { VkRenderPass render_pass; VkPipeline output_pipe_srgb; + VkPipeline output_pipe_pq; VkPipeline output_pipe_lut3d; struct wlr_vk_renderer *renderer; diff --git a/render/vulkan/renderer.c b/render/vulkan/renderer.c index b2feca013..98701626f 100644 --- a/render/vulkan/renderer.c +++ b/render/vulkan/renderer.c @@ -159,6 +159,7 @@ static void destroy_render_format_setup(struct wlr_vk_renderer *renderer, VkDevice dev = renderer->dev->dev; vkDestroyRenderPass(dev, setup->render_pass, NULL); vkDestroyPipeline(dev, setup->output_pipe_srgb, NULL); + vkDestroyPipeline(dev, setup->output_pipe_pq, NULL); vkDestroyPipeline(dev, setup->output_pipe_lut3d, NULL); struct wlr_vk_pipeline *pipeline, *tmp_pipeline; @@ -2299,8 +2300,13 @@ static struct wlr_vk_render_format_setup *find_or_create_render_setup( goto error; } if (!init_blend_to_output_pipeline( - renderer, setup->render_pass, renderer->output_pipe_layout, - &setup->output_pipe_srgb, WLR_VK_OUTPUT_TRANSFORM_INVERSE_SRGB)) { + renderer, setup->render_pass, renderer->output_pipe_layout, + &setup->output_pipe_srgb, WLR_VK_OUTPUT_TRANSFORM_INVERSE_SRGB)) { + goto error; + } + if (!init_blend_to_output_pipeline( + renderer, setup->render_pass, renderer->output_pipe_layout, + &setup->output_pipe_pq, WLR_VK_OUTPUT_TRANSFORM_INVERSE_ST2084_PQ)) { goto error; } } else { diff --git a/render/vulkan/shaders/output.frag b/render/vulkan/shaders/output.frag index 783817707..7478e249d 100644 --- a/render/vulkan/shaders/output.frag +++ b/render/vulkan/shaders/output.frag @@ -18,7 +18,8 @@ layout (constant_id = 0) const int OUTPUT_TRANSFORM = 0; // Matches enum wlr_vk_output_transform #define OUTPUT_TRANSFORM_INVERSE_SRGB 0 -#define OUTPUT_TRANSFORM_LUT_3D 1 +#define OUTPUT_TRANSFORM_INVERSE_ST2084_PQ 1 +#define OUTPUT_TRANSFORM_LUT_3D 2 float linear_channel_to_srgb(float x) { return max(min(x * 12.92, 0.04045), 1.055 * pow(x, 1. / 2.4) - 0.055); @@ -32,6 +33,17 @@ vec3 linear_color_to_srgb(vec3 color) { ); } +vec3 linear_color_to_pq(vec3 color) { + // H.273 TransferCharacteristics code point 16 + float c1 = 0.8359375; + float c2 = 18.8515625; + float c3 = 18.6875; + float m = 78.84375; + float n = 0.1593017578125; + vec3 pow_n = pow(clamp(color, vec3(0), vec3(1)), vec3(n)); + return pow((vec3(c1) + c2 * pow_n) / (vec3(1) + c3 * pow_n), vec3(m)); +} + void main() { vec4 in_color = subpassLoad(in_color).rgba; @@ -50,6 +62,8 @@ void main() { // Apply 3D LUT vec3 pos = data.lut_3d_offset + rgb * data.lut_3d_scale; rgb = texture(lut_3d, pos).rgb; + } else if (OUTPUT_TRANSFORM == OUTPUT_TRANSFORM_INVERSE_ST2084_PQ) { + rgb = linear_color_to_pq(rgb); } else { // OUTPUT_TRANSFORM_INVERSE_SRGB // Produce sRGB encoded values rgb = linear_color_to_srgb(rgb); From 44706835911202e427941c4e76abd8045407225b Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Tue, 28 Jan 2025 12:15:51 +0100 Subject: [PATCH 042/311] render/color, render/vulkan: add support for PQ transfer function --- include/render/color.h | 19 ++++++++++++++++--- include/wlr/render/color.h | 7 ++++--- render/color.c | 19 ++++++++++++++----- render/vulkan/pass.c | 28 ++++++++++++++++++++++------ 4 files changed, 56 insertions(+), 17 deletions(-) diff --git a/include/render/color.h b/include/render/color.h index a797881bf..8730ac6e9 100644 --- a/include/render/color.h +++ b/include/render/color.h @@ -6,7 +6,7 @@ #include enum wlr_color_transform_type { - COLOR_TRANSFORM_SRGB, + COLOR_TRANSFORM_INVERSE_EOTF, COLOR_TRANSFORM_LCMS2, COLOR_TRANSFORM_LUT_3X1D, }; @@ -18,8 +18,11 @@ struct wlr_color_transform { enum wlr_color_transform_type type; }; -void wlr_color_transform_init(struct wlr_color_transform *tr, - enum wlr_color_transform_type type); +struct wlr_color_transform_inverse_eotf { + struct wlr_color_transform base; + + enum wlr_color_transfer_function tf; +}; /** * The formula is approximated via three 1D look-up tables. The flat lut_3x1d @@ -36,6 +39,9 @@ struct wlr_color_transform_lut_3x1d { size_t dim; }; +void wlr_color_transform_init(struct wlr_color_transform *tr, + enum wlr_color_transform_type type); + /** * Get a struct wlr_color_transform_lcms2 from a generic struct wlr_color_transform. * Asserts that the base type is COLOR_TRANSFORM_LCMS2. @@ -51,6 +57,13 @@ void color_transform_lcms2_finish(struct wlr_color_transform_lcms2 *tr); void color_transform_lcms2_eval(struct wlr_color_transform_lcms2 *tr, float out[static 3], const float in[static 3]); +/** + * Gets a wlr_color_transform_inverse_eotf from a generic wlr_color_transform. + * Asserts that the base type is COLOR_TRANSFORM_INVERSE_EOTF + */ +struct wlr_color_transform_inverse_eotf *wlr_color_transform_inverse_eotf_from_base( + struct wlr_color_transform *tr); + /** * Get a struct wlr_color_transform_lut_3x1d from a generic * struct wlr_color_transform. Asserts that the base type is diff --git a/include/wlr/render/color.h b/include/wlr/render/color.h index c2cbee265..42ce5bc30 100644 --- a/include/wlr/render/color.h +++ b/include/wlr/render/color.h @@ -74,10 +74,11 @@ struct wlr_color_transform *wlr_color_transform_init_linear_to_icc( const void *data, size_t size); /** - * Initialize a color transformation to apply sRGB encoding. - * Returns NULL on failure. + * Initialize a color transformation to apply EOTF⁻¹ encoding. Returns + * NULL on failure. */ -struct wlr_color_transform *wlr_color_transform_init_srgb(void); +struct wlr_color_transform *wlr_color_transform_init_linear_to_inverse_eotf( + enum wlr_color_transfer_function tf); /** * Initialize a color transformation to apply three 1D look-up tables. dim diff --git a/render/color.c b/render/color.c index 2bd8d65ec..ca8236515 100644 --- a/render/color.c +++ b/render/color.c @@ -29,13 +29,15 @@ void wlr_color_transform_init(struct wlr_color_transform *tr, enum wlr_color_tra wlr_addon_set_init(&tr->addons); } -struct wlr_color_transform *wlr_color_transform_init_srgb(void) { - struct wlr_color_transform *tx = calloc(1, sizeof(*tx)); +struct wlr_color_transform *wlr_color_transform_init_linear_to_inverse_eotf( + enum wlr_color_transfer_function tf) { + struct wlr_color_transform_inverse_eotf *tx = calloc(1, sizeof(*tx)); if (!tx) { return NULL; } - wlr_color_transform_init(tx, COLOR_TRANSFORM_SRGB); - return tx; + wlr_color_transform_init(&tx->base, COLOR_TRANSFORM_INVERSE_EOTF); + tx->tf = tf; + return &tx->base; } struct wlr_color_transform *wlr_color_transform_init_lut_3x1d(size_t dim, @@ -62,7 +64,7 @@ struct wlr_color_transform *wlr_color_transform_init_lut_3x1d(size_t dim, static void color_transform_destroy(struct wlr_color_transform *tr) { switch (tr->type) { - case COLOR_TRANSFORM_SRGB: + case COLOR_TRANSFORM_INVERSE_EOTF: break; case COLOR_TRANSFORM_LCMS2: color_transform_lcms2_finish(color_transform_lcms2_from_base(tr)); @@ -92,6 +94,13 @@ void wlr_color_transform_unref(struct wlr_color_transform *tr) { } } +struct wlr_color_transform_inverse_eotf *wlr_color_transform_inverse_eotf_from_base( + struct wlr_color_transform *tr) { + assert(tr->type == COLOR_TRANSFORM_INVERSE_EOTF); + struct wlr_color_transform_inverse_eotf *inverse_eotf = wl_container_of(tr, inverse_eotf, base); + return inverse_eotf; +} + struct wlr_color_transform_lut_3x1d *color_transform_lut_3x1d_from_base( struct wlr_color_transform *tr) { assert(tr->type == COLOR_TRANSFORM_LUT_3X1D); diff --git a/render/vulkan/pass.c b/render/vulkan/pass.c index 9ce77eb08..68ee78e67 100644 --- a/render/vulkan/pass.c +++ b/render/vulkan/pass.c @@ -190,7 +190,7 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { struct wlr_vk_color_transform *transform = NULL; size_t dim = 1; - if (pass->color_transform && pass->color_transform->type != COLOR_TRANSFORM_SRGB) { + if (pass->color_transform && pass->color_transform->type != COLOR_TRANSFORM_INVERSE_EOTF) { transform = get_color_transform(pass->color_transform, renderer); assert(transform); dim = transform->lut_3d.dim; @@ -219,11 +219,27 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { } encode_color_matrix(matrix, frag_pcr_data.matrix); - if (pass->color_transform && pass->color_transform->type != COLOR_TRANSFORM_SRGB) { - bind_pipeline(pass, render_buffer->plain.render_setup->output_pipe_lut3d); + VkPipeline pipeline = VK_NULL_HANDLE; + if (pass->color_transform && pass->color_transform->type != COLOR_TRANSFORM_INVERSE_EOTF) { + pipeline = render_buffer->plain.render_setup->output_pipe_lut3d; } else { - bind_pipeline(pass, render_buffer->plain.render_setup->output_pipe_srgb); + enum wlr_color_transfer_function tf = WLR_COLOR_TRANSFER_FUNCTION_SRGB; + if (pass->color_transform && pass->color_transform->type == COLOR_TRANSFORM_INVERSE_EOTF) { + struct wlr_color_transform_inverse_eotf *inverse_eotf = + wlr_color_transform_inverse_eotf_from_base(pass->color_transform); + tf = inverse_eotf->tf; + } + + switch (tf) { + case WLR_COLOR_TRANSFER_FUNCTION_SRGB: + pipeline = render_buffer->plain.render_setup->output_pipe_srgb; + break; + case WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ: + pipeline = render_buffer->plain.render_setup->output_pipe_pq; + break; + } } + bind_pipeline(pass, pipeline); vkCmdPushConstants(render_cb->vk, renderer->output_pipe_layout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(vert_pcr_data), &vert_pcr_data); vkCmdPushConstants(render_cb->vk, renderer->output_pipe_layout, @@ -880,7 +896,7 @@ static bool create_3d_lut_image(struct wlr_vk_renderer *renderer, struct wlr_color_transform_lcms2 *tr_lcms2 = NULL; struct wlr_color_transform_lut_3x1d *tr_lut_3x1d = NULL; switch (tr->type) { - case COLOR_TRANSFORM_SRGB: + case COLOR_TRANSFORM_INVERSE_EOTF: abort(); // unreachable case COLOR_TRANSFORM_LCMS2: tr_lcms2 = color_transform_lcms2_from_base(tr); @@ -1061,7 +1077,7 @@ static struct wlr_vk_color_transform *vk_color_transform_create( return NULL; } - if (transform->type != COLOR_TRANSFORM_SRGB) { + if (transform->type != COLOR_TRANSFORM_INVERSE_EOTF) { vk_transform->lut_3d.dim = 33; if (!create_3d_lut_image(renderer, transform, vk_transform->lut_3d.dim, From dc258b2237df9aaf898efa849bcb8e2d1cddec68 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Tue, 28 Jan 2025 12:40:40 +0100 Subject: [PATCH 043/311] output: add transfer function to image description --- include/wlr/types/wlr_output.h | 4 ++++ types/output/output.c | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/include/wlr/types/wlr_output.h b/include/wlr/types/wlr_output.h index c94b82116..e6cd58824 100644 --- a/include/wlr/types/wlr_output.h +++ b/include/wlr/types/wlr_output.h @@ -89,9 +89,12 @@ enum wlr_output_state_mode_type { * Carries information about the color encoding used for a struct wlr_buffer. * * Supported primaries are advertised in wlr_output.supported_primaries. + * Supported transfer functions are advertised in + * wlr_output.supported_transfer_functions. */ struct wlr_output_image_description { enum wlr_color_named_primaries primaries; + enum wlr_color_transfer_function transfer_function; }; /** @@ -182,6 +185,7 @@ struct wlr_output { int32_t refresh; // mHz, may be zero uint32_t supported_primaries; // bitfield of enum wlr_color_named_primaries + uint32_t supported_transfer_functions; // bitfield of enum wlr_color_transfer_function bool enabled; float scale; diff --git a/types/output/output.c b/types/output/output.c index a0a3f9e02..8576bb0e6 100644 --- a/types/output/output.c +++ b/types/output/output.c @@ -679,6 +679,10 @@ static bool output_basic_test(struct wlr_output *output, wlr_log(WLR_DEBUG, "Unsupported image description primaries"); return false; } + if (!(output->supported_transfer_functions & state->image_description->transfer_function)) { + wlr_log(WLR_DEBUG, "Unsupported image description transfer function"); + return false; + } } return true; From b482e9089bfaa4698966fde476cb88cf66a1faab Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Tue, 28 Jan 2025 12:41:05 +0100 Subject: [PATCH 044/311] backend/drm: add support for image description transfer function --- backend/drm/atomic.c | 43 ++++++++++++++++++++++++++++++++ backend/drm/properties.c | 1 + backend/drm/util.c | 5 ++++ include/backend/drm/drm.h | 2 ++ include/backend/drm/properties.h | 1 + 5 files changed, 52 insertions(+) diff --git a/backend/drm/atomic.c b/backend/drm/atomic.c index 5d42dad5f..a952dc426 100644 --- a/backend/drm/atomic.c +++ b/backend/drm/atomic.c @@ -178,6 +178,37 @@ bool create_fb_damage_clips_blob(struct wlr_drm_backend *drm, return true; } +static uint8_t convert_cta861_eotf(enum wlr_color_transfer_function tf) { + switch (tf) { + case WLR_COLOR_TRANSFER_FUNCTION_SRGB: + abort(); // unsupported + case WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ: + return 2; + } + abort(); // unreachable +} + +static bool create_hdr_output_metadata_blob(struct wlr_drm_backend *drm, + const struct wlr_output_image_description *img_desc, uint32_t *blob_id) { + if (img_desc == NULL) { + *blob_id = 0; + return true; + } + + struct hdr_output_metadata metadata = { + .metadata_type = 0, + .hdmi_metadata_type1 = { + .eotf = convert_cta861_eotf(img_desc->transfer_function), + .metadata_type = 0, + }, + }; + if (drmModeCreatePropertyBlob(drm->fd, &metadata, sizeof(metadata), blob_id) != 0) { + wlr_log_errno(WLR_ERROR, "Failed to create HDR_OUTPUT_METADATA property"); + return false; + } + return true; +} + static uint64_t convert_primaries_to_colorspace(uint32_t primaries) { switch (primaries) { case 0: @@ -318,12 +349,19 @@ bool drm_atomic_connector_prepare(struct wlr_drm_connector_state *state, bool mo state->base->image_description ? state->base->image_description->primaries : 0); } + uint32_t hdr_output_metadata = conn->hdr_output_metadata; + if ((state->base->committed & WLR_OUTPUT_STATE_IMAGE_DESCRIPTION) && + !create_hdr_output_metadata_blob(drm, state->base->image_description, &hdr_output_metadata)) { + return false; + } + state->mode_id = mode_id; state->gamma_lut = gamma_lut; state->fb_damage_clips = fb_damage_clips; state->primary_in_fence_fd = in_fence_fd; state->vrr_enabled = vrr_enabled; state->colorspace = colorspace; + state->hdr_output_metadata = hdr_output_metadata; return true; } @@ -338,6 +376,7 @@ void drm_atomic_connector_apply_commit(struct wlr_drm_connector_state *state) { crtc->own_mode_id = true; commit_blob(drm, &crtc->mode_id, state->mode_id); commit_blob(drm, &crtc->gamma_lut, state->gamma_lut); + commit_blob(drm, &conn->hdr_output_metadata, state->hdr_output_metadata); conn->output.adaptive_sync_status = state->vrr_enabled ? WLR_OUTPUT_ADAPTIVE_SYNC_ENABLED : WLR_OUTPUT_ADAPTIVE_SYNC_DISABLED; @@ -363,6 +402,7 @@ void drm_atomic_connector_rollback_commit(struct wlr_drm_connector_state *state) rollback_blob(drm, &crtc->mode_id, state->mode_id); rollback_blob(drm, &crtc->gamma_lut, state->gamma_lut); + rollback_blob(drm, &conn->hdr_output_metadata, state->hdr_output_metadata); destroy_blob(drm, state->fb_damage_clips); if (state->primary_in_fence_fd >= 0) { @@ -457,6 +497,9 @@ static void atomic_connector_add(struct atomic *atom, if (conn->props.colorspace != 0) { atomic_add(atom, conn->id, conn->props.colorspace, state->colorspace); } + if (conn->props.hdr_output_metadata != 0) { + atomic_add(atom, conn->id, conn->props.hdr_output_metadata, state->hdr_output_metadata); + } atomic_add(atom, crtc->id, crtc->props.mode_id, state->mode_id); atomic_add(atom, crtc->id, crtc->props.active, active); if (active) { diff --git a/backend/drm/properties.c b/backend/drm/properties.c index ca48c0d57..314023954 100644 --- a/backend/drm/properties.c +++ b/backend/drm/properties.c @@ -25,6 +25,7 @@ static const struct prop_info connector_info[] = { { "Colorspace", INDEX(colorspace) }, { "DPMS", INDEX(dpms) }, { "EDID", INDEX(edid) }, + { "HDR_OUTPUT_METADATA", INDEX(hdr_output_metadata) }, { "PATH", INDEX(path) }, { "content type", INDEX(content_type) }, { "link-status", INDEX(link_status) }, diff --git a/backend/drm/util.c b/backend/drm/util.c index 365ebbe08..dd2b37351 100644 --- a/backend/drm/util.c +++ b/backend/drm/util.c @@ -89,6 +89,11 @@ void parse_edid(struct wlr_drm_connector *conn, size_t len, const uint8_t *data) output->supported_primaries |= WLR_COLOR_NAMED_PRIMARIES_BT2020; } + const struct di_hdr_static_metadata *hdr_static_metadata = di_info_get_hdr_static_metadata(info); + if (conn->props.hdr_output_metadata != 0 && hdr_static_metadata->type1 && hdr_static_metadata->pq) { + output->supported_transfer_functions |= WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ; + } + di_info_destroy(info); } diff --git a/include/backend/drm/drm.h b/include/backend/drm/drm.h index c8f26184b..af4231f54 100644 --- a/include/backend/drm/drm.h +++ b/include/backend/drm/drm.h @@ -159,6 +159,7 @@ struct wlr_drm_connector_state { int primary_in_fence_fd, out_fence_fd; bool vrr_enabled; uint32_t colorspace; + uint32_t hdr_output_metadata; }; /** @@ -215,6 +216,7 @@ struct wlr_drm_connector { // Atomic modesetting only uint32_t colorspace; + uint32_t hdr_output_metadata; int32_t refresh; }; diff --git a/include/backend/drm/properties.h b/include/backend/drm/properties.h index 870ea4379..c02d655ba 100644 --- a/include/backend/drm/properties.h +++ b/include/backend/drm/properties.h @@ -27,6 +27,7 @@ struct wlr_drm_connector_props { uint32_t crtc_id; uint32_t colorspace; + uint32_t hdr_output_metadata; }; struct wlr_drm_crtc_props { From 7b6eec530c03e3e611d32269497416e728643897 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Thu, 27 Feb 2025 18:36:15 +0100 Subject: [PATCH 045/311] render/vulkan: add luminance multipler for output shader --- include/render/vulkan.h | 1 + render/vulkan/pass.c | 12 ++++++++++++ render/vulkan/shaders/output.frag | 3 +++ 3 files changed, 16 insertions(+) diff --git a/include/render/vulkan.h b/include/render/vulkan.h index 530fa1d27..b6bd0c2e8 100644 --- a/include/render/vulkan.h +++ b/include/render/vulkan.h @@ -339,6 +339,7 @@ struct wlr_vk_vert_pcr_data { struct wlr_vk_frag_output_pcr_data { float matrix[4][4]; // only a 3x3 subset is used + float luminance_multiplier; float lut_3d_offset; float lut_3d_scale; }; diff --git a/render/vulkan/pass.c b/render/vulkan/pass.c index 68ee78e67..ab044f6a0 100644 --- a/render/vulkan/pass.c +++ b/render/vulkan/pass.c @@ -149,6 +149,11 @@ static VkSemaphore render_pass_wait_sync_file(struct wlr_vk_render_pass *pass, return *sem_ptr; } +static float get_luminance_multiplier(const struct wlr_color_luminances *src_lum, + const struct wlr_color_luminances *dst_lum) { + return (dst_lum->reference / src_lum->reference) * (src_lum->max / dst_lum->max); +} + static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { struct wlr_vk_render_pass *pass = get_render_pass(wlr_pass); struct wlr_vk_renderer *renderer = pass->renderer; @@ -197,6 +202,7 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { } struct wlr_vk_frag_output_pcr_data frag_pcr_data = { + .luminance_multiplier = 1, .lut_3d_offset = 0.5f / dim, .lut_3d_scale = (float)(dim - 1) / dim, }; @@ -238,6 +244,12 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { pipeline = render_buffer->plain.render_setup->output_pipe_pq; break; } + + struct wlr_color_luminances srgb_lum, dst_lum; + wlr_color_transfer_function_get_default_luminance( + WLR_COLOR_TRANSFER_FUNCTION_SRGB, &srgb_lum); + wlr_color_transfer_function_get_default_luminance(tf, &dst_lum); + frag_pcr_data.luminance_multiplier = get_luminance_multiplier(&srgb_lum, &dst_lum); } bind_pipeline(pass, pipeline); vkCmdPushConstants(render_cb->vk, renderer->output_pipe_layout, diff --git a/render/vulkan/shaders/output.frag b/render/vulkan/shaders/output.frag index 7478e249d..f2d5d645c 100644 --- a/render/vulkan/shaders/output.frag +++ b/render/vulkan/shaders/output.frag @@ -10,6 +10,7 @@ layout(location = 0) out vec4 out_color; /* struct wlr_vk_frag_output_pcr_data */ layout(push_constant, row_major) uniform UBO { layout(offset = 80) mat4 matrix; + float luminance_multiplier; float lut_3d_offset; float lut_3d_scale; } data; @@ -56,6 +57,8 @@ void main() { rgb = in_color.rgb / alpha; } + rgb *= data.luminance_multiplier; + rgb = mat3(data.matrix) * rgb; if (OUTPUT_TRANSFORM == OUTPUT_TRANSFORM_LUT_3D) { From f5a09926868d81d81f1029c7ca6077af0974b9ba Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Wed, 18 Jun 2025 21:25:37 +0200 Subject: [PATCH 046/311] render/vulkan: fix multiplication order for output color matrix This had the same bug as the texture side, but I forgot to fix it. See: https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/4988#note_2867416 Fixes: f3524de98095 ("render, render/vulkan: add primaries to wlr_buffer_pass_options") --- render/vulkan/pass.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/render/vulkan/pass.c b/render/vulkan/pass.c index ab044f6a0..290d8e2ca 100644 --- a/render/vulkan/pass.c +++ b/render/vulkan/pass.c @@ -219,7 +219,7 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { float xyz_to_dst_primaries[9]; matrix_invert(xyz_to_dst_primaries, dst_primaries_to_xyz); - wlr_matrix_multiply(matrix, srgb_to_xyz, xyz_to_dst_primaries); + wlr_matrix_multiply(matrix, xyz_to_dst_primaries, srgb_to_xyz); } else { wlr_matrix_identity(matrix); } From 0ee0452af08b7fbf5a4c7a4e3aedb34ad5058a02 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sun, 23 Feb 2025 14:57:04 +0100 Subject: [PATCH 047/311] render/color, render/vulkan: add EXT_LINEAR to enum wlr_color_transfer_function --- backend/drm/atomic.c | 2 ++ include/render/vulkan.h | 8 +++++--- include/wlr/render/color.h | 1 + render/vulkan/pass.c | 3 +++ render/vulkan/renderer.c | 6 ++++++ render/vulkan/shaders/output.frag | 9 +++++---- 6 files changed, 22 insertions(+), 7 deletions(-) diff --git a/backend/drm/atomic.c b/backend/drm/atomic.c index a952dc426..66569232b 100644 --- a/backend/drm/atomic.c +++ b/backend/drm/atomic.c @@ -184,6 +184,8 @@ static uint8_t convert_cta861_eotf(enum wlr_color_transfer_function tf) { abort(); // unsupported case WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ: return 2; + case WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR: + abort(); // unsupported } abort(); // unreachable } diff --git a/include/render/vulkan.h b/include/render/vulkan.h index b6bd0c2e8..1d4b8a0ba 100644 --- a/include/render/vulkan.h +++ b/include/render/vulkan.h @@ -162,9 +162,10 @@ enum wlr_vk_shader_source { // Constants used to pick the color transform for the blend-to-output // fragment shader. Must match those in shaders/output.frag enum wlr_vk_output_transform { - WLR_VK_OUTPUT_TRANSFORM_INVERSE_SRGB = 0, - WLR_VK_OUTPUT_TRANSFORM_INVERSE_ST2084_PQ = 1, - WLR_VK_OUTPUT_TRANSFORM_LUT3D = 2, + WLR_VK_OUTPUT_TRANSFORM_IDENTITY = 0, + WLR_VK_OUTPUT_TRANSFORM_INVERSE_SRGB = 1, + WLR_VK_OUTPUT_TRANSFORM_INVERSE_ST2084_PQ = 2, + WLR_VK_OUTPUT_TRANSFORM_LUT3D = 3, }; struct wlr_vk_pipeline_key { @@ -193,6 +194,7 @@ struct wlr_vk_render_format_setup { bool use_blending_buffer; VkRenderPass render_pass; + VkPipeline output_pipe_identity; VkPipeline output_pipe_srgb; VkPipeline output_pipe_pq; VkPipeline output_pipe_lut3d; diff --git a/include/wlr/render/color.h b/include/wlr/render/color.h index 42ce5bc30..3341668a8 100644 --- a/include/wlr/render/color.h +++ b/include/wlr/render/color.h @@ -27,6 +27,7 @@ enum wlr_color_named_primaries { enum wlr_color_transfer_function { WLR_COLOR_TRANSFER_FUNCTION_SRGB = 1 << 0, WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ = 1 << 1, + WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR = 1 << 2, }; /** diff --git a/render/vulkan/pass.c b/render/vulkan/pass.c index 290d8e2ca..75c454c87 100644 --- a/render/vulkan/pass.c +++ b/render/vulkan/pass.c @@ -237,6 +237,9 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { } switch (tf) { + case WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR: + pipeline = render_buffer->plain.render_setup->output_pipe_identity; + break; case WLR_COLOR_TRANSFER_FUNCTION_SRGB: pipeline = render_buffer->plain.render_setup->output_pipe_srgb; break; diff --git a/render/vulkan/renderer.c b/render/vulkan/renderer.c index 98701626f..cbfb022f7 100644 --- a/render/vulkan/renderer.c +++ b/render/vulkan/renderer.c @@ -158,6 +158,7 @@ static void destroy_render_format_setup(struct wlr_vk_renderer *renderer, VkDevice dev = renderer->dev->dev; vkDestroyRenderPass(dev, setup->render_pass, NULL); + vkDestroyPipeline(dev, setup->output_pipe_identity, NULL); vkDestroyPipeline(dev, setup->output_pipe_srgb, NULL); vkDestroyPipeline(dev, setup->output_pipe_pq, NULL); vkDestroyPipeline(dev, setup->output_pipe_lut3d, NULL); @@ -2294,6 +2295,11 @@ static struct wlr_vk_render_format_setup *find_or_create_render_setup( } // this is only well defined if render pass has a 2nd subpass + if (!init_blend_to_output_pipeline( + renderer, setup->render_pass, renderer->output_pipe_layout, + &setup->output_pipe_identity, WLR_VK_OUTPUT_TRANSFORM_IDENTITY)) { + goto error; + } if (!init_blend_to_output_pipeline( renderer, setup->render_pass, renderer->output_pipe_layout, &setup->output_pipe_lut3d, WLR_VK_OUTPUT_TRANSFORM_LUT3D)) { diff --git a/render/vulkan/shaders/output.frag b/render/vulkan/shaders/output.frag index f2d5d645c..3d5ac4089 100644 --- a/render/vulkan/shaders/output.frag +++ b/render/vulkan/shaders/output.frag @@ -18,9 +18,10 @@ layout(push_constant, row_major) uniform UBO { layout (constant_id = 0) const int OUTPUT_TRANSFORM = 0; // Matches enum wlr_vk_output_transform -#define OUTPUT_TRANSFORM_INVERSE_SRGB 0 -#define OUTPUT_TRANSFORM_INVERSE_ST2084_PQ 1 -#define OUTPUT_TRANSFORM_LUT_3D 2 +#define OUTPUT_TRANSFORM_IDENTITY 0 +#define OUTPUT_TRANSFORM_INVERSE_SRGB 1 +#define OUTPUT_TRANSFORM_INVERSE_ST2084_PQ 2 +#define OUTPUT_TRANSFORM_LUT_3D 3 float linear_channel_to_srgb(float x) { return max(min(x * 12.92, 0.04045), 1.055 * pow(x, 1. / 2.4) - 0.055); @@ -67,7 +68,7 @@ void main() { rgb = texture(lut_3d, pos).rgb; } else if (OUTPUT_TRANSFORM == OUTPUT_TRANSFORM_INVERSE_ST2084_PQ) { rgb = linear_color_to_pq(rgb); - } else { // OUTPUT_TRANSFORM_INVERSE_SRGB + } else if (OUTPUT_TRANSFORM == OUTPUT_TRANSFORM_INVERSE_SRGB) { // Produce sRGB encoded values rgb = linear_color_to_srgb(rgb); } From c8d94000a656934e58ddf1531f7f02499734e20c Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Thu, 27 Feb 2025 23:06:18 +0100 Subject: [PATCH 048/311] color-management-v1: add EXT_LINEAR --- types/wlr_color_management_v1.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/types/wlr_color_management_v1.c b/types/wlr_color_management_v1.c index bdf13768d..b5e9a65fa 100644 --- a/types/wlr_color_management_v1.c +++ b/types/wlr_color_management_v1.c @@ -81,6 +81,8 @@ static enum wlr_color_transfer_function transfer_function_to_wlr( return WLR_COLOR_TRANSFER_FUNCTION_SRGB; case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST2084_PQ: return WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ; + case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR: + return WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR; default: abort(); } From dd3d9be41e02ff5b0dfce2eb6cf009322acbfbf2 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sun, 23 Feb 2025 14:58:04 +0100 Subject: [PATCH 049/311] render/pass: add wlr_render_texture_options.transfer_function Also add a bit in wlr_renderer.features to indicate support. --- include/wlr/render/pass.h | 3 +++ include/wlr/render/wlr_renderer.h | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/include/wlr/render/pass.h b/include/wlr/render/pass.h index 1323a8c9b..b8854bbf9 100644 --- a/include/wlr/render/pass.h +++ b/include/wlr/render/pass.h @@ -12,6 +12,7 @@ #include #include #include +#include #include struct wlr_renderer; @@ -101,6 +102,8 @@ struct wlr_render_texture_options { enum wlr_scale_filter_mode filter_mode; /* Blend mode */ enum wlr_render_blend_mode blend_mode; + /* Transfer function the source texture is encoded with */ + enum wlr_color_transfer_function transfer_function; /* Wait for a timeline synchronization point before texturing. * diff --git a/include/wlr/render/wlr_renderer.h b/include/wlr/render/wlr_renderer.h index debeb4e29..62a1cca2d 100644 --- a/include/wlr/render/wlr_renderer.h +++ b/include/wlr/render/wlr_renderer.h @@ -41,6 +41,10 @@ struct wlr_renderer { } events; struct { + /** + * Whether color transforms are supported for input textures + */ + bool input_color_transform; /** * Does the renderer support color transforms on its output? */ From b1a9dab03e6e406b8ef71a0bcc8281ed6c2773aa Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sun, 23 Feb 2025 15:08:04 +0100 Subject: [PATCH 050/311] render/vulkan: fix typo in wlr_vk_texture.views comment --- include/render/vulkan.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/render/vulkan.h b/include/render/vulkan.h index 1d4b8a0ba..e2846565c 100644 --- a/include/render/vulkan.h +++ b/include/render/vulkan.h @@ -478,7 +478,7 @@ struct wlr_vk_texture { struct wlr_buffer *buffer; struct wlr_addon buffer_addon; - struct wl_list views; // struct wlr_vk_texture_ds.link + struct wl_list views; // struct wlr_vk_texture_view.link }; struct wlr_vk_texture *vulkan_get_texture(struct wlr_texture *wlr_texture); From 8d1c6e42ac708927b814518cb7f9e48863b79743 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sun, 23 Feb 2025 15:08:50 +0100 Subject: [PATCH 051/311] render/vulkan: add support for texture transfer functions --- include/render/vulkan.h | 6 +++--- render/vulkan/pass.c | 12 ++++++++++-- render/vulkan/renderer.c | 1 + render/vulkan/texture.c | 14 ++++++++------ 4 files changed, 22 insertions(+), 11 deletions(-) diff --git a/include/render/vulkan.h b/include/render/vulkan.h index e2846565c..651106042 100644 --- a/include/render/vulkan.h +++ b/include/render/vulkan.h @@ -349,6 +349,7 @@ struct wlr_vk_frag_output_pcr_data { struct wlr_vk_texture_view { struct wl_list link; // struct wlr_vk_texture.views const struct wlr_vk_pipeline_layout *layout; + bool srgb; VkDescriptorSet ds; VkImageView image_view; @@ -363,7 +364,7 @@ struct wlr_vk_pipeline_layout *get_or_create_pipeline_layout( const struct wlr_vk_pipeline_layout_key *key); struct wlr_vk_texture_view *vulkan_texture_get_or_create_view( struct wlr_vk_texture *texture, - const struct wlr_vk_pipeline_layout *layout); + const struct wlr_vk_pipeline_layout *layout, bool srgb); // Creates a vulkan renderer for the given device. struct wlr_renderer *vulkan_renderer_create_for_device(struct wlr_vk_device *dev); @@ -463,13 +464,12 @@ struct wlr_vk_texture { VkDeviceMemory memories[WLR_DMABUF_MAX_PLANES]; VkImage image; const struct wlr_vk_format *format; - enum wlr_vk_texture_transform transform; struct wlr_vk_command_buffer *last_used_cb; // to track when it can be destroyed bool dmabuf_imported; bool owned; // if dmabuf_imported: whether we have ownership of the image bool transitioned; // if dma_imported: whether we transitioned it away from preinit bool has_alpha; // whether the image is has alpha channel - bool using_mutable_srgb; // is this accessed through _SRGB format view + bool using_mutable_srgb; // can be accessed through _SRGB format view struct wl_list foreign_link; // wlr_vk_renderer.foreign_textures struct wl_list destroy_link; // wlr_vk_command_buffer.destroy_textures struct wl_list link; // wlr_vk_renderer.textures diff --git a/render/vulkan/pass.c b/render/vulkan/pass.c index 75c454c87..95fbd319e 100644 --- a/render/vulkan/pass.c +++ b/render/vulkan/pass.c @@ -781,6 +781,14 @@ static void render_pass_add_texture(struct wlr_render_pass *wlr_pass, }; encode_proj_matrix(matrix, vert_pcr_data.mat4); + bool srgb = options->transfer_function == 0 || + options->transfer_function == WLR_COLOR_TRANSFER_FUNCTION_SRGB; + bool srgb_image_view = srgb && texture->using_mutable_srgb; + enum wlr_vk_texture_transform tex_transform = + srgb_image_view || options->transfer_function == WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR ? + WLR_VK_TEXTURE_TRANSFORM_IDENTITY : + WLR_VK_TEXTURE_TRANSFORM_SRGB; + struct wlr_vk_render_format_setup *setup = pass->srgb_pathway ? pass->render_buffer->srgb.render_setup : pass->render_buffer->plain.render_setup; @@ -792,7 +800,7 @@ static void render_pass_add_texture(struct wlr_render_pass *wlr_pass, .ycbcr_format = texture->format->is_ycbcr ? texture->format : NULL, .filter_mode = options->filter_mode, }, - .texture_transform = texture->transform, + .texture_transform = tex_transform, .blend_mode = !texture->has_alpha && alpha == 1.0 ? WLR_RENDER_BLEND_MODE_NONE : options->blend_mode, }); @@ -802,7 +810,7 @@ static void render_pass_add_texture(struct wlr_render_pass *wlr_pass, } struct wlr_vk_texture_view *view = - vulkan_texture_get_or_create_view(texture, pipe->layout); + vulkan_texture_get_or_create_view(texture, pipe->layout, srgb_image_view); if (!view) { pass->failed = true; return; diff --git a/render/vulkan/renderer.c b/render/vulkan/renderer.c index cbfb022f7..a0e456580 100644 --- a/render/vulkan/renderer.c +++ b/render/vulkan/renderer.c @@ -2442,6 +2442,7 @@ struct wlr_renderer *vulkan_renderer_create_for_device(struct wlr_vk_device *dev renderer->dev = dev; wlr_renderer_init(&renderer->wlr_renderer, &renderer_impl, WLR_BUFFER_CAP_DMABUF); + renderer->wlr_renderer.features.input_color_transform = true; renderer->wlr_renderer.features.output_color_transform = true; wl_list_init(&renderer->stage.buffers); wl_list_init(&renderer->foreign_textures); diff --git a/render/vulkan/texture.c b/render/vulkan/texture.c index 61a6040ee..2b21d458c 100644 --- a/render/vulkan/texture.c +++ b/render/vulkan/texture.c @@ -269,10 +269,12 @@ static struct wlr_vk_texture *vulkan_texture_create( } struct wlr_vk_texture_view *vulkan_texture_get_or_create_view(struct wlr_vk_texture *texture, - const struct wlr_vk_pipeline_layout *pipeline_layout) { + const struct wlr_vk_pipeline_layout *pipeline_layout, bool srgb) { + assert(texture->using_mutable_srgb || !srgb); + struct wlr_vk_texture_view *view; wl_list_for_each(view, &texture->views, link) { - if (view->layout == pipeline_layout) { + if (view->layout == pipeline_layout && view->srgb == srgb) { return view; } } @@ -283,6 +285,7 @@ struct wlr_vk_texture_view *vulkan_texture_get_or_create_view(struct wlr_vk_text } view->layout = pipeline_layout; + view->srgb = srgb; VkResult res; VkDevice dev = texture->renderer->dev->dev; @@ -290,8 +293,7 @@ struct wlr_vk_texture_view *vulkan_texture_get_or_create_view(struct wlr_vk_text VkImageViewCreateInfo view_info = { .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .viewType = VK_IMAGE_VIEW_TYPE_2D, - .format = texture->using_mutable_srgb ? texture->format->vk_srgb - : texture->format->vk, + .format = srgb ? texture->format->vk_srgb : texture->format->vk, .components.r = VK_COMPONENT_SWIZZLE_IDENTITY, .components.g = VK_COMPONENT_SWIZZLE_IDENTITY, .components.b = VK_COMPONENT_SWIZZLE_IDENTITY, @@ -353,10 +355,10 @@ struct wlr_vk_texture_view *vulkan_texture_get_or_create_view(struct wlr_vk_text static void texture_set_format(struct wlr_vk_texture *texture, const struct wlr_vk_format *format, bool has_mutable_srgb) { + assert(!(format->is_ycbcr && has_mutable_srgb)); + texture->format = format; texture->using_mutable_srgb = has_mutable_srgb; - texture->transform = !format->is_ycbcr && has_mutable_srgb ? - WLR_VK_TEXTURE_TRANSFORM_IDENTITY : WLR_VK_TEXTURE_TRANSFORM_SRGB; const struct wlr_pixel_format_info *format_info = drm_get_pixel_format_info(format->drm); From 4efec11721c9d779b5bf72fe3baac120e8da55c8 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sun, 23 Feb 2025 15:09:34 +0100 Subject: [PATCH 052/311] scene: add transfer function support for wlr_scene_buffer --- include/wlr/types/wlr_scene.h | 4 ++++ types/scene/wlr_scene.c | 14 ++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/include/wlr/types/wlr_scene.h b/include/wlr/types/wlr_scene.h index 114d5c0f3..9f27b4373 100644 --- a/include/wlr/types/wlr_scene.h +++ b/include/wlr/types/wlr_scene.h @@ -192,6 +192,7 @@ struct wlr_scene_buffer { int dst_width, dst_height; enum wl_output_transform transform; pixman_region32_t opaque_region; + enum wlr_color_transfer_function transfer_function; struct { uint64_t active_outputs; @@ -541,6 +542,9 @@ void wlr_scene_buffer_set_opacity(struct wlr_scene_buffer *scene_buffer, void wlr_scene_buffer_set_filter_mode(struct wlr_scene_buffer *scene_buffer, enum wlr_scale_filter_mode filter_mode); +void wlr_scene_buffer_set_transfer_function(struct wlr_scene_buffer *scene_buffer, + enum wlr_color_transfer_function transfer_function); + /** * Calls the buffer's frame_done signal. */ diff --git a/types/scene/wlr_scene.c b/types/scene/wlr_scene.c index 1acbb5650..baa36bf3a 100644 --- a/types/scene/wlr_scene.c +++ b/types/scene/wlr_scene.c @@ -1099,6 +1099,16 @@ void wlr_scene_buffer_set_filter_mode(struct wlr_scene_buffer *scene_buffer, scene_node_update(&scene_buffer->node, NULL); } +void wlr_scene_buffer_set_transfer_function(struct wlr_scene_buffer *scene_buffer, + enum wlr_color_transfer_function transfer_function) { + if (scene_buffer->transfer_function == transfer_function) { + return; + } + + scene_buffer->transfer_function = transfer_function; + scene_node_update(&scene_buffer->node, NULL); +} + static struct wlr_texture *scene_buffer_get_texture( struct wlr_scene_buffer *scene_buffer, struct wlr_renderer *renderer) { if (scene_buffer->buffer == NULL || scene_buffer->texture != NULL) { @@ -1446,6 +1456,7 @@ static void scene_entry_render(struct render_list_entry *entry, const struct ren .blend_mode = !data->output->scene->calculate_visibility || !pixman_region32_empty(&opaque) ? WLR_RENDER_BLEND_MODE_PREMULTIPLIED : WLR_RENDER_BLEND_MODE_NONE, + .transfer_function = scene_buffer->transfer_function, .wait_timeline = scene_buffer->wait_timeline, .wait_point = scene_buffer->wait_point, }); @@ -1936,6 +1947,9 @@ static enum scene_direct_scanout_result scene_entry_try_direct_scanout( if (buffer->transform != data->transform) { return SCANOUT_INELIGIBLE; } + if (buffer->transfer_function != 0 && buffer->transfer_function != WLR_COLOR_TRANSFER_FUNCTION_SRGB) { + return false; + } // We want to ensure optimal buffer selection, but as direct-scanout can be enabled and disabled // on a frame-by-frame basis, we wait for a few frames to send the new format recommendations. From 7a1161438c34233754f96ded95da681a3abae862 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sun, 23 Feb 2025 15:09:59 +0100 Subject: [PATCH 053/311] scene: add support for color-management-v1 transfer functions --- types/scene/surface.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/types/scene/surface.c b/types/scene/surface.c index 1973abcaa..82ee12e07 100644 --- a/types/scene/surface.c +++ b/types/scene/surface.c @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -186,11 +187,31 @@ static void surface_reconfigure(struct wlr_scene_surface *scene_surface) { opacity = (float)alpha_modifier_state->multiplier; } + enum wlr_color_transfer_function tf = WLR_COLOR_TRANSFER_FUNCTION_SRGB; + const struct wlr_image_description_v1_data *img_desc = + wlr_surface_get_image_description_v1_data(surface); + if (img_desc != NULL) { + switch (img_desc->tf_named) { + case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_SRGB: + tf = WLR_COLOR_TRANSFER_FUNCTION_SRGB; + break; + case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST2084_PQ: + tf = WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ; + break; + case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR: + tf = WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR; + break; + default: + abort(); + } + } + wlr_scene_buffer_set_opaque_region(scene_buffer, &opaque); wlr_scene_buffer_set_source_box(scene_buffer, &src_box); wlr_scene_buffer_set_dest_size(scene_buffer, width, height); wlr_scene_buffer_set_transform(scene_buffer, state->transform); wlr_scene_buffer_set_opacity(scene_buffer, opacity); + wlr_scene_buffer_set_transfer_function(scene_buffer, tf); scene_buffer_unmark_client_buffer(scene_buffer); From ec422ac38914a3a642a5ff16ac264fe99b2ff1fb Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Thu, 27 Feb 2025 16:22:17 +0100 Subject: [PATCH 054/311] render/vulkan: prepare texture shader for new transforms --- render/vulkan/shaders/texture.frag | 36 +++++++++++++++++++----------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/render/vulkan/shaders/texture.frag b/render/vulkan/shaders/texture.frag index 6f2f347de..84b7678e4 100644 --- a/render/vulkan/shaders/texture.frag +++ b/render/vulkan/shaders/texture.frag @@ -21,27 +21,37 @@ float srgb_channel_to_linear(float x) { x > 0.04045); } -vec4 srgb_color_to_linear(vec4 color) { - if (color.a == 0) { - return vec4(0); - } - color.rgb /= color.a; - color.rgb = vec3( +vec3 srgb_color_to_linear(vec3 color) { + return vec3( srgb_channel_to_linear(color.r), srgb_channel_to_linear(color.g), srgb_channel_to_linear(color.b) ); - color.rgb *= color.a; - return color; } void main() { - vec4 val = textureLod(tex, uv, 0); - if (TEXTURE_TRANSFORM == TEXTURE_TRANSFORM_SRGB) { - out_color = srgb_color_to_linear(val); - } else { // TEXTURE_TRANSFORM_IDENTITY - out_color = val; + vec4 in_color = textureLod(tex, uv, 0); + + if (TEXTURE_TRANSFORM == TEXTURE_TRANSFORM_IDENTITY) { + out_color = in_color * data.alpha; + return; } + // Convert from pre-multiplied alpha to straight alpha + float alpha = in_color.a; + vec3 rgb; + if (alpha == 0) { + rgb = vec3(0); + } else { + rgb = in_color.rgb / alpha; + } + + if (TEXTURE_TRANSFORM == TEXTURE_TRANSFORM_SRGB) { + rgb = srgb_color_to_linear(rgb); + } + + // Back to pre-multiplied alpha + out_color = vec4(rgb * alpha, alpha); + out_color *= data.alpha; } From 56d95c2ecb2fb9f2d4ab103419b9d247ce171fc1 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Thu, 27 Feb 2025 16:48:32 +0100 Subject: [PATCH 055/311] render/vulkan: introduce wlr_vk_frag_texture_pcr_data Contains UBOs for texture.frag. --- include/render/vulkan.h | 4 ++++ render/vulkan/pass.c | 8 ++++++-- render/vulkan/shaders/texture.frag | 1 + 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/include/render/vulkan.h b/include/render/vulkan.h index 651106042..c3fb6b2bf 100644 --- a/include/render/vulkan.h +++ b/include/render/vulkan.h @@ -339,6 +339,10 @@ struct wlr_vk_vert_pcr_data { float uv_size[2]; }; +struct wlr_vk_frag_texture_pcr_data { + float alpha; +}; + struct wlr_vk_frag_output_pcr_data { float matrix[4][4]; // only a 3x3 subset is used float luminance_multiplier; diff --git a/render/vulkan/pass.c b/render/vulkan/pass.c index 95fbd319e..49435b0e8 100644 --- a/render/vulkan/pass.c +++ b/render/vulkan/pass.c @@ -816,6 +816,10 @@ static void render_pass_add_texture(struct wlr_render_pass *wlr_pass, return; } + struct wlr_vk_frag_texture_pcr_data frag_pcr_data = { + .alpha = alpha, + }; + bind_pipeline(pass, pipe->vk); vkCmdBindDescriptorSets(cb, VK_PIPELINE_BIND_POINT_GRAPHICS, @@ -824,8 +828,8 @@ static void render_pass_add_texture(struct wlr_render_pass *wlr_pass, vkCmdPushConstants(cb, pipe->layout->vk, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(vert_pcr_data), &vert_pcr_data); vkCmdPushConstants(cb, pipe->layout->vk, - VK_SHADER_STAGE_FRAGMENT_BIT, sizeof(vert_pcr_data), sizeof(float), - &alpha); + VK_SHADER_STAGE_FRAGMENT_BIT, sizeof(vert_pcr_data), + sizeof(frag_pcr_data), &frag_pcr_data); pixman_region32_t clip; get_clip_region(pass, options->clip, &clip); diff --git a/render/vulkan/shaders/texture.frag b/render/vulkan/shaders/texture.frag index 84b7678e4..58ac59fea 100644 --- a/render/vulkan/shaders/texture.frag +++ b/render/vulkan/shaders/texture.frag @@ -5,6 +5,7 @@ layout(set = 0, binding = 0) uniform sampler2D tex; layout(location = 0) in vec2 uv; layout(location = 0) out vec4 out_color; +// struct wlr_vk_frag_texture_pcr_data layout(push_constant) uniform UBO { layout(offset = 80) float alpha; } data; From 3a51a5c6238fa5351cfc25d6a4af554b31877ec4 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Thu, 27 Feb 2025 16:57:00 +0100 Subject: [PATCH 056/311] render/vulkan: add texture color transformation matrix --- include/render/vulkan.h | 1 + render/vulkan/pass.c | 4 ++++ render/vulkan/shaders/texture.frag | 12 +++++------- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/include/render/vulkan.h b/include/render/vulkan.h index c3fb6b2bf..00cc33ea0 100644 --- a/include/render/vulkan.h +++ b/include/render/vulkan.h @@ -340,6 +340,7 @@ struct wlr_vk_vert_pcr_data { }; struct wlr_vk_frag_texture_pcr_data { + float matrix[4][4]; // only a 3x3 subset is used float alpha; }; diff --git a/render/vulkan/pass.c b/render/vulkan/pass.c index 49435b0e8..68be473cf 100644 --- a/render/vulkan/pass.c +++ b/render/vulkan/pass.c @@ -816,9 +816,13 @@ static void render_pass_add_texture(struct wlr_render_pass *wlr_pass, return; } + float color_matrix[9]; + wlr_matrix_identity(color_matrix); + struct wlr_vk_frag_texture_pcr_data frag_pcr_data = { .alpha = alpha, }; + encode_color_matrix(color_matrix, frag_pcr_data.matrix); bind_pipeline(pass, pipe->vk); diff --git a/render/vulkan/shaders/texture.frag b/render/vulkan/shaders/texture.frag index 58ac59fea..dc4f60cfe 100644 --- a/render/vulkan/shaders/texture.frag +++ b/render/vulkan/shaders/texture.frag @@ -6,8 +6,9 @@ layout(location = 0) in vec2 uv; layout(location = 0) out vec4 out_color; // struct wlr_vk_frag_texture_pcr_data -layout(push_constant) uniform UBO { - layout(offset = 80) float alpha; +layout(push_constant, row_major) uniform UBO { + layout(offset = 80) mat4 matrix; + float alpha; } data; layout (constant_id = 0) const int TEXTURE_TRANSFORM = 0; @@ -33,11 +34,6 @@ vec3 srgb_color_to_linear(vec3 color) { void main() { vec4 in_color = textureLod(tex, uv, 0); - if (TEXTURE_TRANSFORM == TEXTURE_TRANSFORM_IDENTITY) { - out_color = in_color * data.alpha; - return; - } - // Convert from pre-multiplied alpha to straight alpha float alpha = in_color.a; vec3 rgb; @@ -51,6 +47,8 @@ void main() { rgb = srgb_color_to_linear(rgb); } + rgb = mat3(data.matrix) * rgb; + // Back to pre-multiplied alpha out_color = vec4(rgb * alpha, alpha); From a8144088df077f60ce7b09c79c00eda5cb56683c Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Thu, 27 Feb 2025 16:37:00 +0100 Subject: [PATCH 057/311] render/vulkan: add support for PQ for textures --- include/render/vulkan.h | 1 + render/vulkan/pass.c | 30 +++++++++++++++++++++++------- render/vulkan/shaders/texture.frag | 14 ++++++++++++++ 3 files changed, 38 insertions(+), 7 deletions(-) diff --git a/include/render/vulkan.h b/include/render/vulkan.h index 00cc33ea0..1ed69d6e4 100644 --- a/include/render/vulkan.h +++ b/include/render/vulkan.h @@ -152,6 +152,7 @@ struct wlr_vk_pipeline_layout { enum wlr_vk_texture_transform { WLR_VK_TEXTURE_TRANSFORM_IDENTITY = 0, WLR_VK_TEXTURE_TRANSFORM_SRGB = 1, + WLR_VK_TEXTURE_TRANSFORM_ST2084_PQ = 2, }; enum wlr_vk_shader_source { diff --git a/render/vulkan/pass.c b/render/vulkan/pass.c index 68be473cf..30cd0fe54 100644 --- a/render/vulkan/pass.c +++ b/render/vulkan/pass.c @@ -781,13 +781,29 @@ static void render_pass_add_texture(struct wlr_render_pass *wlr_pass, }; encode_proj_matrix(matrix, vert_pcr_data.mat4); - bool srgb = options->transfer_function == 0 || - options->transfer_function == WLR_COLOR_TRANSFER_FUNCTION_SRGB; - bool srgb_image_view = srgb && texture->using_mutable_srgb; - enum wlr_vk_texture_transform tex_transform = - srgb_image_view || options->transfer_function == WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR ? - WLR_VK_TEXTURE_TRANSFORM_IDENTITY : - WLR_VK_TEXTURE_TRANSFORM_SRGB; + enum wlr_color_transfer_function tf = options->transfer_function; + if (tf == 0) { + tf = WLR_COLOR_TRANSFER_FUNCTION_SRGB; + } + + bool srgb_image_view = false; + enum wlr_vk_texture_transform tex_transform = 0; + switch (tf) { + case WLR_COLOR_TRANSFER_FUNCTION_SRGB: + if (texture->using_mutable_srgb) { + tex_transform = WLR_VK_TEXTURE_TRANSFORM_IDENTITY; + srgb_image_view = true; + } else { + tex_transform = WLR_VK_TEXTURE_TRANSFORM_SRGB; + } + break; + case WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR: + tex_transform = WLR_VK_TEXTURE_TRANSFORM_IDENTITY; + break; + case WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ: + tex_transform = WLR_VK_TEXTURE_TRANSFORM_ST2084_PQ; + break; + } struct wlr_vk_render_format_setup *setup = pass->srgb_pathway ? pass->render_buffer->srgb.render_setup : diff --git a/render/vulkan/shaders/texture.frag b/render/vulkan/shaders/texture.frag index dc4f60cfe..e1467023b 100644 --- a/render/vulkan/shaders/texture.frag +++ b/render/vulkan/shaders/texture.frag @@ -16,6 +16,7 @@ layout (constant_id = 0) const int TEXTURE_TRANSFORM = 0; // Matches enum wlr_vk_texture_transform #define TEXTURE_TRANSFORM_IDENTITY 0 #define TEXTURE_TRANSFORM_SRGB 1 +#define TEXTURE_TRANSFORM_ST2084_PQ 2 float srgb_channel_to_linear(float x) { return mix(x / 12.92, @@ -31,6 +32,17 @@ vec3 srgb_color_to_linear(vec3 color) { ); } +vec3 pq_color_to_linear(vec3 color) { + float inv_m1 = 1 / 0.1593017578125; + float inv_m2 = 1 / 78.84375; + float c1 = 0.8359375; + float c2 = 18.8515625; + float c3 = 18.6875; + vec3 num = max(pow(color, vec3(inv_m2)) - c1, 0); + vec3 denom = c2 - c3 * pow(color, vec3(inv_m2)); + return pow(num / denom, vec3(inv_m1)); +} + void main() { vec4 in_color = textureLod(tex, uv, 0); @@ -45,6 +57,8 @@ void main() { if (TEXTURE_TRANSFORM == TEXTURE_TRANSFORM_SRGB) { rgb = srgb_color_to_linear(rgb); + } else if (TEXTURE_TRANSFORM == TEXTURE_TRANSFORM_ST2084_PQ) { + rgb = pq_color_to_linear(rgb); } rgb = mat3(data.matrix) * rgb; From fa1feb447f6d417ff4a73262709aaccf94efaa2c Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Thu, 27 Feb 2025 17:52:37 +0100 Subject: [PATCH 058/311] render, render/vulkan: add primaries to wlr_render_texture_options --- include/wlr/render/pass.h | 2 ++ render/vulkan/pass.c | 16 +++++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/include/wlr/render/pass.h b/include/wlr/render/pass.h index b8854bbf9..8e22bdf8f 100644 --- a/include/wlr/render/pass.h +++ b/include/wlr/render/pass.h @@ -104,6 +104,8 @@ struct wlr_render_texture_options { enum wlr_render_blend_mode blend_mode; /* Transfer function the source texture is encoded with */ enum wlr_color_transfer_function transfer_function; + /* Primaries describing the color volume of the source texture */ + const struct wlr_color_primaries *primaries; /* Wait for a timeline synchronization point before texturing. * diff --git a/render/vulkan/pass.c b/render/vulkan/pass.c index 30cd0fe54..0ff4f52c6 100644 --- a/render/vulkan/pass.c +++ b/render/vulkan/pass.c @@ -833,7 +833,21 @@ static void render_pass_add_texture(struct wlr_render_pass *wlr_pass, } float color_matrix[9]; - wlr_matrix_identity(color_matrix); + if (options->primaries != NULL) { + struct wlr_color_primaries srgb; + wlr_color_primaries_from_named(&srgb, WLR_COLOR_NAMED_PRIMARIES_SRGB); + + float src_primaries_to_xyz[9]; + wlr_color_primaries_to_xyz(options->primaries, src_primaries_to_xyz); + float srgb_to_xyz[9]; + wlr_color_primaries_to_xyz(&srgb, srgb_to_xyz); + float xyz_to_srgb[9]; + matrix_invert(xyz_to_srgb, srgb_to_xyz); + + wlr_matrix_multiply(color_matrix, xyz_to_srgb, src_primaries_to_xyz); + } else { + wlr_matrix_identity(color_matrix); + } struct wlr_vk_frag_texture_pcr_data frag_pcr_data = { .alpha = alpha, From ae85c31176728d1b3944844572bf6c7c690b9698 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sat, 1 Mar 2025 12:43:08 +0100 Subject: [PATCH 059/311] render/vulkan: add luminance multiplier for texture shader --- include/render/vulkan.h | 1 + render/vulkan/pass.c | 10 ++++++++++ render/vulkan/shaders/texture.frag | 3 +++ 3 files changed, 14 insertions(+) diff --git a/include/render/vulkan.h b/include/render/vulkan.h index 1ed69d6e4..deff0eac3 100644 --- a/include/render/vulkan.h +++ b/include/render/vulkan.h @@ -343,6 +343,7 @@ struct wlr_vk_vert_pcr_data { struct wlr_vk_frag_texture_pcr_data { float matrix[4][4]; // only a 3x3 subset is used float alpha; + float luminance_multiplier; }; struct wlr_vk_frag_output_pcr_data { diff --git a/render/vulkan/pass.c b/render/vulkan/pass.c index 0ff4f52c6..4fd357113 100644 --- a/render/vulkan/pass.c +++ b/render/vulkan/pass.c @@ -849,8 +849,18 @@ static void render_pass_add_texture(struct wlr_render_pass *wlr_pass, wlr_matrix_identity(color_matrix); } + float luminance_multiplier = 1; + if (tf != WLR_COLOR_TRANSFER_FUNCTION_SRGB) { + struct wlr_color_luminances src_lum, srgb_lum; + wlr_color_transfer_function_get_default_luminance(tf, &src_lum); + wlr_color_transfer_function_get_default_luminance( + WLR_COLOR_TRANSFER_FUNCTION_SRGB, &srgb_lum); + luminance_multiplier = get_luminance_multiplier(&src_lum, &srgb_lum); + } + struct wlr_vk_frag_texture_pcr_data frag_pcr_data = { .alpha = alpha, + .luminance_multiplier = luminance_multiplier, }; encode_color_matrix(color_matrix, frag_pcr_data.matrix); diff --git a/render/vulkan/shaders/texture.frag b/render/vulkan/shaders/texture.frag index e1467023b..2a7e2c517 100644 --- a/render/vulkan/shaders/texture.frag +++ b/render/vulkan/shaders/texture.frag @@ -9,6 +9,7 @@ layout(location = 0) out vec4 out_color; layout(push_constant, row_major) uniform UBO { layout(offset = 80) mat4 matrix; float alpha; + float luminance_multiplier; } data; layout (constant_id = 0) const int TEXTURE_TRANSFORM = 0; @@ -61,6 +62,8 @@ void main() { rgb = pq_color_to_linear(rgb); } + rgb *= data.luminance_multiplier; + rgb = mat3(data.matrix) * rgb; // Back to pre-multiplied alpha From 071773cb27bc628302b6adb0c2ab5bedb85788cb Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Thu, 27 Feb 2025 18:07:43 +0100 Subject: [PATCH 060/311] scene: add primaries support to wlr_scene_buffer --- include/wlr/types/wlr_scene.h | 4 ++++ types/scene/wlr_scene.c | 20 ++++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/include/wlr/types/wlr_scene.h b/include/wlr/types/wlr_scene.h index 9f27b4373..5d4a056ce 100644 --- a/include/wlr/types/wlr_scene.h +++ b/include/wlr/types/wlr_scene.h @@ -193,6 +193,7 @@ struct wlr_scene_buffer { enum wl_output_transform transform; pixman_region32_t opaque_region; enum wlr_color_transfer_function transfer_function; + enum wlr_color_named_primaries primaries; struct { uint64_t active_outputs; @@ -545,6 +546,9 @@ void wlr_scene_buffer_set_filter_mode(struct wlr_scene_buffer *scene_buffer, void wlr_scene_buffer_set_transfer_function(struct wlr_scene_buffer *scene_buffer, enum wlr_color_transfer_function transfer_function); +void wlr_scene_buffer_set_primaries(struct wlr_scene_buffer *scene_buffer, + enum wlr_color_named_primaries primaries); + /** * Calls the buffer's frame_done signal. */ diff --git a/types/scene/wlr_scene.c b/types/scene/wlr_scene.c index baa36bf3a..144329491 100644 --- a/types/scene/wlr_scene.c +++ b/types/scene/wlr_scene.c @@ -14,6 +14,7 @@ #include #include #include +#include "render/color.h" #include "types/wlr_output.h" #include "types/wlr_scene.h" #include "util/array.h" @@ -1109,6 +1110,16 @@ void wlr_scene_buffer_set_transfer_function(struct wlr_scene_buffer *scene_buffe scene_node_update(&scene_buffer->node, NULL); } +void wlr_scene_buffer_set_primaries(struct wlr_scene_buffer *scene_buffer, + enum wlr_color_named_primaries primaries) { + if (scene_buffer->primaries == primaries) { + return; + } + + scene_buffer->primaries = primaries; + scene_node_update(&scene_buffer->node, NULL); +} + static struct wlr_texture *scene_buffer_get_texture( struct wlr_scene_buffer *scene_buffer, struct wlr_renderer *renderer) { if (scene_buffer->buffer == NULL || scene_buffer->texture != NULL) { @@ -1445,6 +1456,11 @@ static void scene_entry_render(struct render_list_entry *entry, const struct ren wlr_output_transform_invert(scene_buffer->transform); transform = wlr_output_transform_compose(transform, data->transform); + struct wlr_color_primaries primaries = {0}; + if (scene_buffer->primaries != 0) { + wlr_color_primaries_from_named(&primaries, scene_buffer->primaries); + } + wlr_render_pass_add_texture(data->render_pass, &(struct wlr_render_texture_options) { .texture = texture, .src_box = scene_buffer->src_box, @@ -1457,6 +1473,7 @@ static void scene_entry_render(struct render_list_entry *entry, const struct ren !pixman_region32_empty(&opaque) ? WLR_RENDER_BLEND_MODE_PREMULTIPLIED : WLR_RENDER_BLEND_MODE_NONE, .transfer_function = scene_buffer->transfer_function, + .primaries = scene_buffer->primaries != 0 ? &primaries : NULL, .wait_timeline = scene_buffer->wait_timeline, .wait_point = scene_buffer->wait_point, }); @@ -1950,6 +1967,9 @@ static enum scene_direct_scanout_result scene_entry_try_direct_scanout( if (buffer->transfer_function != 0 && buffer->transfer_function != WLR_COLOR_TRANSFER_FUNCTION_SRGB) { return false; } + if (buffer->primaries != 0 && buffer->primaries != WLR_COLOR_NAMED_PRIMARIES_SRGB) { + return false; + } // We want to ensure optimal buffer selection, but as direct-scanout can be enabled and disabled // on a frame-by-frame basis, we wait for a few frames to send the new format recommendations. From 0c272a38423679e980d4cd39affc202fd48c0f6d Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Thu, 27 Feb 2025 18:13:50 +0100 Subject: [PATCH 061/311] scene: add support for color-management-v1 primaries --- types/scene/surface.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/types/scene/surface.c b/types/scene/surface.c index 82ee12e07..07ef7ebe4 100644 --- a/types/scene/surface.c +++ b/types/scene/surface.c @@ -188,6 +188,7 @@ static void surface_reconfigure(struct wlr_scene_surface *scene_surface) { } enum wlr_color_transfer_function tf = WLR_COLOR_TRANSFER_FUNCTION_SRGB; + enum wlr_color_named_primaries primaries = WLR_COLOR_NAMED_PRIMARIES_SRGB; const struct wlr_image_description_v1_data *img_desc = wlr_surface_get_image_description_v1_data(surface); if (img_desc != NULL) { @@ -204,6 +205,17 @@ static void surface_reconfigure(struct wlr_scene_surface *scene_surface) { default: abort(); } + + switch (img_desc->primaries_named) { + case WP_COLOR_MANAGER_V1_PRIMARIES_SRGB: + primaries = WLR_COLOR_NAMED_PRIMARIES_SRGB; + break; + case WP_COLOR_MANAGER_V1_PRIMARIES_BT2020: + primaries = WLR_COLOR_NAMED_PRIMARIES_BT2020; + break; + default: + abort(); + } } wlr_scene_buffer_set_opaque_region(scene_buffer, &opaque); @@ -212,6 +224,7 @@ static void surface_reconfigure(struct wlr_scene_surface *scene_surface) { wlr_scene_buffer_set_transform(scene_buffer, state->transform); wlr_scene_buffer_set_opacity(scene_buffer, opacity); wlr_scene_buffer_set_transfer_function(scene_buffer, tf); + wlr_scene_buffer_set_primaries(scene_buffer, primaries); scene_buffer_unmark_client_buffer(scene_buffer); From 98af3371755338ccbcd021c1bbacec2ca7b47806 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Fri, 20 Jun 2025 19:02:54 +0200 Subject: [PATCH 062/311] output: shorten output enabled checks Use a more concise loop instead of repeated logic. No behavior change. --- types/output/output.c | 46 ++++++++++++++++++------------------------- 1 file changed, 19 insertions(+), 27 deletions(-) diff --git a/types/output/output.c b/types/output/output.c index 8576bb0e6..4f268c78d 100644 --- a/types/output/output.c +++ b/types/output/output.c @@ -627,33 +627,25 @@ static bool output_basic_test(struct wlr_output *output, } } - if (!enabled && state->committed & WLR_OUTPUT_STATE_BUFFER) { - wlr_log(WLR_DEBUG, "Tried to commit a buffer on a disabled output"); - return false; - } - if (!enabled && state->committed & WLR_OUTPUT_STATE_MODE) { - wlr_log(WLR_DEBUG, "Tried to modeset a disabled output"); - return false; - } - if (!enabled && state->committed & WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED) { - wlr_log(WLR_DEBUG, "Tried to enable adaptive sync on a disabled output"); - return false; - } - if (!enabled && state->committed & WLR_OUTPUT_STATE_RENDER_FORMAT) { - wlr_log(WLR_DEBUG, "Tried to set format for a disabled output"); - return false; - } - if (!enabled && state->committed & WLR_OUTPUT_STATE_SUBPIXEL) { - wlr_log(WLR_DEBUG, "Tried to set the subpixel layout on a disabled output"); - return false; - } - if (!enabled && state->committed & WLR_OUTPUT_STATE_COLOR_TRANSFORM) { - wlr_log(WLR_DEBUG, "Tried to set a color transform on a disabled output"); - return false; - } - if (!enabled && state->committed & WLR_OUTPUT_STATE_IMAGE_DESCRIPTION) { - wlr_log(WLR_DEBUG, "Tried to set the image description on a disabled output"); - return false; + const struct { + enum wlr_output_state_field field; + const char *name; + } needs_enabled[] = { + { WLR_OUTPUT_STATE_BUFFER, "buffer" }, + { WLR_OUTPUT_STATE_MODE, "mode" }, + { WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED, "adaptive sync" }, + { WLR_OUTPUT_STATE_RENDER_FORMAT, "render format" }, + { WLR_OUTPUT_STATE_SUBPIXEL, "subpixel" }, + { WLR_OUTPUT_STATE_COLOR_TRANSFORM, "color transform" }, + { WLR_OUTPUT_STATE_IMAGE_DESCRIPTION, "image description" }, + }; + if (!enabled) { + for (size_t i = 0; i < sizeof(needs_enabled) / sizeof(needs_enabled[0]); i++) { + if (state->committed & needs_enabled[i].field) { + wlr_log(WLR_DEBUG, "Tried to set %s on a disabled output", needs_enabled[i].name); + return false; + } + } } if (state->committed & WLR_OUTPUT_STATE_LAYERS) { From f5e7caf59994cfa08650cade41374e23779a24a4 Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Wed, 18 Jun 2025 13:30:21 +0200 Subject: [PATCH 063/311] util/box: set dest to empty if boxes don't intersect Currently if both box_a and box_b are non-empty but do not intersect, this function does not set dest to an empty box. This contradicts the doc comments and is surprising for users. --- util/box.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/util/box.c b/util/box.c index 6a6becf33..a9b42579a 100644 --- a/util/box.c +++ b/util/box.c @@ -67,7 +67,12 @@ bool wlr_box_intersection(struct wlr_box *dest, const struct wlr_box *box_a, dest->width = x2 - x1; dest->height = y2 - y1; - return !wlr_box_empty(dest); + if (wlr_box_empty(dest)) { + *dest = (struct wlr_box){0}; + return false; + } + + return true; } bool wlr_box_contains_point(const struct wlr_box *box, double x, double y) { From 6d8bb66f98700472d05e57b8199d621e1cefe338 Mon Sep 17 00:00:00 2001 From: tokyo4j Date: Sun, 25 May 2025 16:50:27 +0900 Subject: [PATCH 064/311] xwm: add support for _NET_WM_ICON --- include/wlr/xwayland/xwayland.h | 10 ++++++++ include/xwayland/xwm.h | 1 + xwayland/xwm.c | 44 ++++++++++++++++++++++++++++++--- 3 files changed, 51 insertions(+), 4 deletions(-) diff --git a/include/wlr/xwayland/xwayland.h b/include/wlr/xwayland/xwayland.h index 92a45a493..1c52b3558 100644 --- a/include/wlr/xwayland/xwayland.h +++ b/include/wlr/xwayland/xwayland.h @@ -221,6 +221,7 @@ struct wlr_xwayland_surface { struct wl_signal set_override_redirect; struct wl_signal set_geometry; struct wl_signal set_opacity; + struct wl_signal set_icon; struct wl_signal focus_in; struct wl_signal grab_focus; /* can be used to set initial maximized/fullscreen geometry */ @@ -401,6 +402,15 @@ enum wlr_xwayland_icccm_input_model wlr_xwayland_surface_icccm_input_model( void wlr_xwayland_set_workareas(struct wlr_xwayland *wlr_xwayland, const struct wlr_box *workareas, size_t num_workareas); +/** + * Fetches the icon set via the _NET_WM_ICON property. + * + * Returns true on success. The caller is responsible for freeing the reply + * using xcb_ewmh_get_wm_icon_reply_wipe(). + */ +bool wlr_xwayland_surface_fetch_icon( + const struct wlr_xwayland_surface *xsurface, + xcb_ewmh_get_wm_icon_reply_t *icon_reply); /** * Get the XCB connection of the XWM. diff --git a/include/xwayland/xwm.h b/include/xwayland/xwm.h index d38e5cc91..e460bbb63 100644 --- a/include/xwayland/xwm.h +++ b/include/xwayland/xwm.h @@ -36,6 +36,7 @@ enum atom_name { NET_WM_STATE, NET_WM_STRUT_PARTIAL, NET_WM_WINDOW_TYPE, + NET_WM_ICON, WM_TAKE_FOCUS, WINDOW, NET_ACTIVE_WINDOW, diff --git a/xwayland/xwm.c b/xwayland/xwm.c index bcdcd218c..a82e8b145 100644 --- a/xwayland/xwm.c +++ b/xwayland/xwm.c @@ -34,6 +34,7 @@ static const char *const atom_map[ATOM_LAST] = { [NET_WM_STATE] = "_NET_WM_STATE", [NET_WM_STRUT_PARTIAL] = "_NET_WM_STRUT_PARTIAL", [NET_WM_WINDOW_TYPE] = "_NET_WM_WINDOW_TYPE", + [NET_WM_ICON] = "_NET_WM_ICON", [WM_TAKE_FOCUS] = "WM_TAKE_FOCUS", [WINDOW] = "WINDOW", [NET_ACTIVE_WINDOW] = "_NET_ACTIVE_WINDOW", @@ -241,6 +242,7 @@ static struct wlr_xwayland_surface *xwayland_surface_create( wl_signal_init(&surface->events.set_override_redirect); wl_signal_init(&surface->events.set_geometry); wl_signal_init(&surface->events.set_opacity); + wl_signal_init(&surface->events.set_icon); wl_signal_init(&surface->events.focus_in); wl_signal_init(&surface->events.grab_focus); wl_signal_init(&surface->events.map_request); @@ -602,6 +604,7 @@ static void xwayland_surface_destroy(struct wlr_xwayland_surface *xsurface) { assert(wl_list_empty(&xsurface->events.set_override_redirect.listener_list)); assert(wl_list_empty(&xsurface->events.set_geometry.listener_list)); assert(wl_list_empty(&xsurface->events.set_opacity.listener_list)); + assert(wl_list_empty(&xsurface->events.set_icon.listener_list)); assert(wl_list_empty(&xsurface->events.focus_in.listener_list)); assert(wl_list_empty(&xsurface->events.grab_focus.listener_list)); assert(wl_list_empty(&xsurface->events.map_request.listener_list)); @@ -1078,6 +1081,8 @@ static void read_surface_property(struct wlr_xwm *xwm, // intentionally ignored } else if (property == xwm->atoms[NET_WM_WINDOW_TYPE]) { read_surface_window_type(xwm, xsurface, reply); + } else if (property == xwm->atoms[NET_WM_ICON]) { + wl_signal_emit_mutable(&xsurface->events.set_icon, NULL); } else if (property == xwm->atoms[WM_PROTOCOLS]) { read_surface_protocols(xwm, xsurface, reply); } else if (property == xwm->atoms[NET_WM_STATE]) { @@ -1131,6 +1136,38 @@ static const struct wlr_addon_interface surface_addon_impl = { .destroy = xwayland_surface_handle_addon_destroy, }; +bool wlr_xwayland_surface_fetch_icon( + const struct wlr_xwayland_surface *xsurface, + xcb_ewmh_get_wm_icon_reply_t *icon_reply) { + struct wlr_xwm *xwm = xsurface->xwm; + + xcb_get_property_cookie_t cookie = xcb_get_property(xwm->xcb_conn, 0, + xsurface->window_id, xwm->atoms[NET_WM_ICON], XCB_ATOM_CARDINAL, + 0, UINT32_MAX); + xcb_get_property_reply_t *reply = + xcb_get_property_reply(xwm->xcb_conn, cookie, NULL); + if (!reply) { + return false; + } + + if (!xcb_ewmh_get_wm_icon_from_reply(icon_reply, reply)) { + free(reply); + return false; + } + + return true; +} + +static xcb_get_property_cookie_t get_property(struct wlr_xwm *xwm, + xcb_window_t window_id, xcb_atom_t atom) { + uint32_t len = 2048; + if (atom == xwm->atoms[NET_WM_ICON]) { + /* Compositors need to fetch icon data wlr_xwayland_surface_fetch_icon() */ + len = 0; + } + return xcb_get_property(xwm->xcb_conn, 0, window_id, atom, XCB_ATOM_ANY, 0, len); +} + static void xwayland_surface_associate(struct wlr_xwm *xwm, struct wlr_xwayland_surface *xsurface, struct wlr_surface *surface) { assert(xsurface->surface == NULL); @@ -1165,12 +1202,12 @@ static void xwayland_surface_associate(struct wlr_xwm *xwm, xwm->atoms[NET_WM_STRUT_PARTIAL], xwm->atoms[NET_WM_WINDOW_TYPE], xwm->atoms[NET_WM_NAME], + xwm->atoms[NET_WM_ICON], }; xcb_get_property_cookie_t cookies[sizeof(props) / sizeof(props[0])] = {0}; for (size_t i = 0; i < sizeof(props) / sizeof(props[0]); i++) { - cookies[i] = xcb_get_property(xwm->xcb_conn, 0, xsurface->window_id, - props[i], XCB_ATOM_ANY, 0, 2048); + cookies[i] = get_property(xwm, xsurface->window_id, props[i]); } for (size_t i = 0; i < sizeof(props) / sizeof(props[0]); i++) { @@ -1398,8 +1435,7 @@ static void xwm_handle_property_notify(struct wlr_xwm *xwm, return; } - xcb_get_property_cookie_t cookie = - xcb_get_property(xwm->xcb_conn, 0, xsurface->window_id, ev->atom, XCB_ATOM_ANY, 0, 2048); + xcb_get_property_cookie_t cookie = get_property(xwm, xsurface->window_id, ev->atom); xcb_get_property_reply_t *reply = xcb_get_property_reply(xwm->xcb_conn, cookie, NULL); if (reply == NULL) { From e76f8ac2b3407fa7d027831a81324c6cb5a5301f Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Thu, 19 Jun 2025 13:34:19 +0200 Subject: [PATCH 065/311] output: add wlr_output.image_description Stores the current image description. --- include/wlr/types/wlr_output.h | 2 ++ types/output/output.c | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/include/wlr/types/wlr_output.h b/include/wlr/types/wlr_output.h index e6cd58824..1ef289008 100644 --- a/include/wlr/types/wlr_output.h +++ b/include/wlr/types/wlr_output.h @@ -193,6 +193,7 @@ struct wlr_output { enum wl_output_transform transform; enum wlr_output_adaptive_sync_status adaptive_sync_status; uint32_t render_format; + const struct wlr_output_image_description *image_description; // Indicates whether making changes to adaptive sync status is supported. // If false, changes to adaptive sync status is guaranteed to fail. If @@ -255,6 +256,7 @@ struct wlr_output { struct { struct wl_listener display_destroy; + struct wlr_output_image_description image_description_value; } WLR_PRIVATE; }; diff --git a/types/output/output.c b/types/output/output.c index 4f268c78d..3410138c1 100644 --- a/types/output/output.c +++ b/types/output/output.c @@ -233,6 +233,15 @@ static void output_apply_state(struct wlr_output *output, output->transform = state->transform; } + if (state->committed & WLR_OUTPUT_STATE_IMAGE_DESCRIPTION) { + if (state->image_description != NULL) { + output->image_description_value = *state->image_description; + output->image_description = &output->image_description_value; + } else { + output->image_description = NULL; + } + } + bool geometry_updated = state->committed & (WLR_OUTPUT_STATE_MODE | WLR_OUTPUT_STATE_TRANSFORM | WLR_OUTPUT_STATE_SUBPIXEL); From 2498036e673af7e82b79de85a529c07dcdaed712 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Thu, 19 Jun 2025 14:09:28 +0200 Subject: [PATCH 066/311] output: add output_pending_image_description() --- include/types/wlr_output.h | 2 ++ types/output/output.c | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/include/types/wlr_output.h b/include/types/wlr_output.h index 2dc979c66..f901505af 100644 --- a/include/types/wlr_output.h +++ b/include/types/wlr_output.h @@ -8,6 +8,8 @@ void output_pending_resolution(struct wlr_output *output, const struct wlr_output_state *state, int *width, int *height); bool output_pending_enabled(struct wlr_output *output, const struct wlr_output_state *state); +const struct wlr_output_image_description *output_pending_image_description( + struct wlr_output *output, const struct wlr_output_state *state); bool output_pick_format(struct wlr_output *output, const struct wlr_drm_format_set *display_formats, diff --git a/types/output/output.c b/types/output/output.c index 3410138c1..636d155d2 100644 --- a/types/output/output.c +++ b/types/output/output.c @@ -500,6 +500,14 @@ bool output_pending_enabled(struct wlr_output *output, return output->enabled; } +const struct wlr_output_image_description *output_pending_image_description( + struct wlr_output *output, const struct wlr_output_state *state) { + if (state->committed & WLR_OUTPUT_STATE_IMAGE_DESCRIPTION) { + return state->image_description; + } + return output->image_description; +} + /** * Compare a struct wlr_output_state with the current state of a struct * wlr_output. From bf40f396bfd0934b4927265ae1c05a16649551df Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Thu, 19 Jun 2025 14:09:51 +0200 Subject: [PATCH 067/311] scene: grab image description from output state Alternative to https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/5089 --- include/wlr/types/wlr_scene.h | 5 +++++ types/scene/wlr_scene.c | 27 +++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/include/wlr/types/wlr_scene.h b/include/wlr/types/wlr_scene.h index 5d4a056ce..0f4aa4c27 100644 --- a/include/wlr/types/wlr_scene.h +++ b/include/wlr/types/wlr_scene.h @@ -574,6 +574,11 @@ void wlr_scene_output_set_position(struct wlr_scene_output *scene_output, struct wlr_scene_output_state_options { struct wlr_scene_timer *timer; + + /** + * Color transform to apply before the output's color transform. Cannot be + * used when the output has a non-NULL image description set. + */ struct wlr_color_transform *color_transform; /** diff --git a/types/scene/wlr_scene.c b/types/scene/wlr_scene.c index 144329491..8c58ea2b1 100644 --- a/types/scene/wlr_scene.c +++ b/types/scene/wlr_scene.c @@ -1964,6 +1964,17 @@ static enum scene_direct_scanout_result scene_entry_try_direct_scanout( if (buffer->transform != data->transform) { return SCANOUT_INELIGIBLE; } + + const struct wlr_output_image_description *img_desc = output_pending_image_description(scene_output->output, state); + if (buffer->transfer_function != 0 || buffer->primaries != 0) { + if (img_desc == NULL || img_desc->transfer_function != buffer->transfer_function || + img_desc->primaries != buffer->primaries) { + return false; + } + } else if (img_desc != NULL) { + return false; + } + if (buffer->transfer_function != 0 && buffer->transfer_function != WLR_COLOR_TRANSFER_FUNCTION_SRGB) { return false; } @@ -2269,14 +2280,30 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, timer->pre_render_duration = timespec_to_nsec(&duration); } + struct wlr_color_transform *color_transform = NULL; + const struct wlr_color_primaries *primaries = NULL; + struct wlr_color_primaries primaries_value; + const struct wlr_output_image_description *img_desc = output_pending_image_description(output, state); + if (img_desc != NULL) { + color_transform = wlr_color_transform_init_linear_to_inverse_eotf(img_desc->transfer_function); + wlr_color_primaries_from_named(&primaries_value, img_desc->primaries); + primaries = &primaries_value; + } + if (options->color_transform != NULL) { + assert(color_transform == NULL); + color_transform = wlr_color_transform_ref(options->color_transform); + } + scene_output->in_point++; struct wlr_render_pass *render_pass = wlr_renderer_begin_buffer_pass(output->renderer, buffer, &(struct wlr_buffer_pass_options){ .timer = timer ? timer->render_timer : NULL, .color_transform = options->color_transform, + .primaries = primaries, .signal_timeline = scene_output->in_timeline, .signal_point = scene_output->in_point, }); + wlr_color_transform_unref(color_transform); if (render_pass == NULL) { wlr_buffer_unlock(buffer); return false; From aecb867098f575fc97809c652b92c78b078b533b Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Wed, 25 Jun 2025 08:49:19 +0200 Subject: [PATCH 068/311] output: add full HDR metadata to wlr_output_image_description This allows sinks to improve their tone mapping. --- include/wlr/types/wlr_output.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/include/wlr/types/wlr_output.h b/include/wlr/types/wlr_output.h index 1ef289008..6a0dd0455 100644 --- a/include/wlr/types/wlr_output.h +++ b/include/wlr/types/wlr_output.h @@ -91,10 +91,20 @@ enum wlr_output_state_mode_type { * Supported primaries are advertised in wlr_output.supported_primaries. * Supported transfer functions are advertised in * wlr_output.supported_transfer_functions. + * + * mastering_display_primaries, mastering_luminance, max_cll and max_fall are + * optional. Luminances are given in cd/m². */ struct wlr_output_image_description { enum wlr_color_named_primaries primaries; enum wlr_color_transfer_function transfer_function; + + struct wlr_color_primaries mastering_display_primaries; + struct { + double min, max; + } mastering_luminance; + double max_cll; // max content light level + double max_fall; // max frame-average light level }; /** From 8c7041c4e842c9fb029a6371eb53f73aa98e7b31 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Wed, 25 Jun 2025 08:54:02 +0200 Subject: [PATCH 069/311] backend/drm: relay full HDR metadata --- backend/drm/atomic.c | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/backend/drm/atomic.c b/backend/drm/atomic.c index 66569232b..33ce6f42d 100644 --- a/backend/drm/atomic.c +++ b/backend/drm/atomic.c @@ -190,6 +190,16 @@ static uint8_t convert_cta861_eotf(enum wlr_color_transfer_function tf) { abort(); // unreachable } +static uint16_t convert_cta861_color_coord(double v) { + if (v < 0) { + v = 0; + } + if (v > 1) { + v = 1; + } + return (uint16_t)round(v * 50000); +} + static bool create_hdr_output_metadata_blob(struct wlr_drm_backend *drm, const struct wlr_output_image_description *img_desc, uint32_t *blob_id) { if (img_desc == NULL) { @@ -202,6 +212,28 @@ static bool create_hdr_output_metadata_blob(struct wlr_drm_backend *drm, .hdmi_metadata_type1 = { .eotf = convert_cta861_eotf(img_desc->transfer_function), .metadata_type = 0, + .display_primaries = { + { + .x = convert_cta861_color_coord(img_desc->mastering_display_primaries.red.x), + .y = convert_cta861_color_coord(img_desc->mastering_display_primaries.red.y), + }, + { + .x = convert_cta861_color_coord(img_desc->mastering_display_primaries.green.x), + .y = convert_cta861_color_coord(img_desc->mastering_display_primaries.green.y), + }, + { + .x = convert_cta861_color_coord(img_desc->mastering_display_primaries.blue.x), + .y = convert_cta861_color_coord(img_desc->mastering_display_primaries.blue.y), + }, + }, + .white_point = { + .x = convert_cta861_color_coord(img_desc->mastering_display_primaries.white.x), + .y = convert_cta861_color_coord(img_desc->mastering_display_primaries.white.y), + }, + .max_display_mastering_luminance = img_desc->mastering_luminance.max, + .min_display_mastering_luminance = img_desc->mastering_luminance.min * 0.0001, + .max_cll = img_desc->max_cll, + .max_fall = img_desc->max_fall, }, }; if (drmModeCreatePropertyBlob(drm->fd, &metadata, sizeof(metadata), blob_id) != 0) { From 48bd1831feff89ac94aacc2218601c2b569ef70c Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sun, 29 Jun 2025 10:57:56 +0200 Subject: [PATCH 070/311] render/egl: fix software rendering check Commit b4ce0d8b39a4 ("render/egl: accept negative DRM FD to select software rendering") added an EXT_device_drm check to figure out whether the user selected a device with a DRM FD or without one. However, for KMS-only devices, Mesa will never advertise the selected KMS node: https://gitlab.freedesktop.org/mesa/mesa/-/blob/3f1d40d230c98d4ca9d2e20b214723a7b7d265d2/src/egl/main/egldevice.c#L109 Instead, pass down a parameter to indicate whether a DRM FD was passed in. Fixes: b4ce0d8b39a4 ("render/egl: accept negative DRM FD to select software rendering") --- render/egl.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/render/egl.c b/render/egl.c index 07d3a1425..6f3e9c8ca 100644 --- a/render/egl.c +++ b/render/egl.c @@ -260,7 +260,8 @@ static struct wlr_egl *egl_create(void) { return egl; } -static bool egl_init_display(struct wlr_egl *egl, EGLDisplay display) { +static bool egl_init_display(struct wlr_egl *egl, EGLDisplay display, + bool allow_software) { egl->display = display; EGLint major, minor; @@ -326,9 +327,8 @@ static bool egl_init_display(struct wlr_egl *egl, EGLDisplay display) { // The only way a non-DRM device is selected is when the user // explicitly picks software rendering - if (check_egl_ext(device_exts_str, "EGL_MESA_device_software") && - egl->exts.EXT_device_drm) { - if (env_parse_bool("WLR_RENDERER_ALLOW_SOFTWARE")) { + if (check_egl_ext(device_exts_str, "EGL_MESA_device_software")) { + if (allow_software || env_parse_bool("WLR_RENDERER_ALLOW_SOFTWARE")) { wlr_log(WLR_INFO, "Using software rendering"); } else { wlr_log(WLR_ERROR, "Software rendering detected, please use " @@ -382,7 +382,7 @@ static bool egl_init_display(struct wlr_egl *egl, EGLDisplay display) { } static bool egl_init(struct wlr_egl *egl, EGLenum platform, - void *remote_display) { + void *remote_display, bool allow_software) { EGLint display_attribs[3] = {0}; size_t display_attribs_len = 0; @@ -401,7 +401,7 @@ static bool egl_init(struct wlr_egl *egl, EGLenum platform, return false; } - if (!egl_init_display(egl, display)) { + if (!egl_init_display(egl, display, allow_software)) { if (egl->exts.KHR_display_reference) { eglTerminate(display); } @@ -556,6 +556,8 @@ static int open_render_node(int drm_fd) { } struct wlr_egl *wlr_egl_create_with_drm_fd(int drm_fd) { + bool allow_software = drm_fd < 0; + struct wlr_egl *egl = egl_create(); if (egl == NULL) { wlr_log(WLR_ERROR, "Failed to create EGL context"); @@ -569,7 +571,7 @@ struct wlr_egl *wlr_egl_create_with_drm_fd(int drm_fd) { */ EGLDeviceEXT egl_device = get_egl_device_from_drm_fd(egl, drm_fd); if (egl_device != EGL_NO_DEVICE_EXT) { - if (egl_init(egl, EGL_PLATFORM_DEVICE_EXT, egl_device)) { + if (egl_init(egl, EGL_PLATFORM_DEVICE_EXT, egl_device, allow_software)) { wlr_log(WLR_DEBUG, "Using EGL_PLATFORM_DEVICE_EXT"); return egl; } @@ -594,7 +596,7 @@ struct wlr_egl *wlr_egl_create_with_drm_fd(int drm_fd) { goto error; } - if (egl_init(egl, EGL_PLATFORM_GBM_KHR, egl->gbm_device)) { + if (egl_init(egl, EGL_PLATFORM_GBM_KHR, egl->gbm_device, allow_software)) { wlr_log(WLR_DEBUG, "Using EGL_PLATFORM_GBM_KHR"); return egl; } @@ -633,7 +635,7 @@ struct wlr_egl *wlr_egl_create_with_context(EGLDisplay display, return NULL; } - if (!egl_init_display(egl, display)) { + if (!egl_init_display(egl, display, true)) { free(egl); return NULL; } From 58c3680d96bffa1b8f59c274050a6ecfa27e7c23 Mon Sep 17 00:00:00 2001 From: David Turner Date: Mon, 30 Jun 2025 15:45:03 +0100 Subject: [PATCH 071/311] scene: Block damage on single-pixel buffer textures We cache whether buffers are single-pixel buffers (and if so what color they are) to allow rendering optimizations. But this breaks if the client changes out the single-pixel buffer for one with a different color, because this updates the texture in-place instead of actually changing the buffer. We can fix this by blocking in-place texture updates for single pixel buffers. Original bug: https://codeberg.org/ifreund/waylock/issues/121 See also: !5092 --- types/scene/surface.c | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/types/scene/surface.c b/types/scene/surface.c index 07ef7ebe4..1ee0e3134 100644 --- a/types/scene/surface.c +++ b/types/scene/surface.c @@ -127,8 +127,11 @@ static void scene_buffer_unmark_client_buffer(struct wlr_scene_buffer *scene_buf return; } - assert(buffer->n_ignore_locks > 0); - buffer->n_ignore_locks--; + // If the buffer was a single-pixel buffer where we cached its color + // then it won't have been marked as damage-allowed. + if (buffer->n_ignore_locks > 0) { + buffer->n_ignore_locks--; + } } static int min(int a, int b) { @@ -229,7 +232,22 @@ static void surface_reconfigure(struct wlr_scene_surface *scene_surface) { scene_buffer_unmark_client_buffer(scene_buffer); if (surface->buffer) { - client_buffer_mark_next_can_damage(surface->buffer); + // If we've cached the buffer's single-pixel buffer color + // then any in-place updates to the texture wouldn't be + // reflected in rendering. So only allow in-place texture + // updates if it's not a single pixel buffer. Note that we + // can't use the cached scene_buffer->is_single_pixel_buffer + // because that's only set later on. + bool is_single_pixel_buffer = false; + struct wlr_client_buffer *client_buffer = wlr_client_buffer_get(&surface->buffer->base); + if (client_buffer != NULL && client_buffer->source != NULL) { + struct wlr_single_pixel_buffer_v1 *spb = + wlr_single_pixel_buffer_v1_try_from_buffer(client_buffer->source); + is_single_pixel_buffer = spb != NULL; + } + if (!is_single_pixel_buffer) { + client_buffer_mark_next_can_damage(surface->buffer); + } struct wlr_linux_drm_syncobj_surface_v1_state *syncobj_surface_state = wlr_linux_drm_syncobj_v1_get_surface_state(surface); From 31b78a4f3afe86b0406bcde89ca8db1b5f338d85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Poisot?= Date: Thu, 3 Jul 2025 12:01:07 +0000 Subject: [PATCH 072/311] scene: fix output transfer functions fixes: bf40f396b --- types/scene/wlr_scene.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/scene/wlr_scene.c b/types/scene/wlr_scene.c index 8c58ea2b1..d2327eda2 100644 --- a/types/scene/wlr_scene.c +++ b/types/scene/wlr_scene.c @@ -2298,7 +2298,7 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, struct wlr_render_pass *render_pass = wlr_renderer_begin_buffer_pass(output->renderer, buffer, &(struct wlr_buffer_pass_options){ .timer = timer ? timer->render_timer : NULL, - .color_transform = options->color_transform, + .color_transform = color_transform, .primaries = primaries, .signal_timeline = scene_output->in_timeline, .signal_point = scene_output->in_point, From 6aa654b728e2f30e2f3ca3c2057fc10161c78161 Mon Sep 17 00:00:00 2001 From: Consolatis <40171-Consolatis@users.noreply.gitlab.freedesktop.org> Date: Sun, 6 Jul 2025 14:08:12 +0200 Subject: [PATCH 073/311] wlr_text_input_v3: remove event arguments from header Fixes 2d5492c73770c9de420527df1098fefabe43d689 --- include/wlr/types/wlr_text_input_v3.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/include/wlr/types/wlr_text_input_v3.h b/include/wlr/types/wlr_text_input_v3.h index f9eb1d7ed..29feb7e6c 100644 --- a/include/wlr/types/wlr_text_input_v3.h +++ b/include/wlr/types/wlr_text_input_v3.h @@ -57,10 +57,10 @@ struct wlr_text_input_v3 { struct wl_list link; struct { - struct wl_signal enable; // struct wlr_text_input_v3 - struct wl_signal commit; // struct wlr_text_input_v3 - struct wl_signal disable; // struct wlr_text_input_v3 - struct wl_signal destroy; // struct wlr_text_input_v3 + struct wl_signal enable; + struct wl_signal commit; + struct wl_signal disable; + struct wl_signal destroy; } events; struct { @@ -75,7 +75,7 @@ struct wlr_text_input_manager_v3 { struct { struct wl_signal new_text_input; // struct wlr_text_input_v3 - struct wl_signal destroy; // struct wlr_text_input_manager_v3 + struct wl_signal destroy; } events; struct { From f4327f52cf18fea41c08e054012b5a85967b18b1 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Thu, 3 Apr 2025 15:21:15 +0200 Subject: [PATCH 074/311] xdg-toplevel-tag-v1: new protocol References: https://gitlab.freedesktop.org/wayland/wayland-protocols/-/merge_requests/238 --- include/wlr/types/wlr_xdg_toplevel_tag_v1.h | 41 ++++++++ protocol/meson.build | 1 + types/meson.build | 1 + types/wlr_xdg_toplevel_tag_v1.c | 104 ++++++++++++++++++++ 4 files changed, 147 insertions(+) create mode 100644 include/wlr/types/wlr_xdg_toplevel_tag_v1.h create mode 100644 types/wlr_xdg_toplevel_tag_v1.c diff --git a/include/wlr/types/wlr_xdg_toplevel_tag_v1.h b/include/wlr/types/wlr_xdg_toplevel_tag_v1.h new file mode 100644 index 000000000..8318cab02 --- /dev/null +++ b/include/wlr/types/wlr_xdg_toplevel_tag_v1.h @@ -0,0 +1,41 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_XDG_TOPLEVEL_TAG_V1_H +#define WLR_TYPES_WLR_XDG_TOPLEVEL_TAG_V1_H + +#include + +struct wlr_xdg_toplevel_tag_manager_v1 { + struct wl_global *global; + + struct { + struct wl_signal set_tag; // struct wlr_xdg_toplevel_tag_manager_v1_set_tag_event + struct wl_signal set_description; // struct wlr_xdg_toplevel_tag_manager_v1_set_description_event + struct wl_signal destroy; + } events; + + struct { + struct wl_listener display_destroy; + } WLR_PRIVATE; +}; + +struct wlr_xdg_toplevel_tag_manager_v1_set_tag_event { + struct wlr_xdg_toplevel *toplevel; + const char *tag; +}; + +struct wlr_xdg_toplevel_tag_manager_v1_set_description_event { + struct wlr_xdg_toplevel *toplevel; + const char *description; +}; + +struct wlr_xdg_toplevel_tag_manager_v1 *wlr_xdg_toplevel_tag_manager_v1_create( + struct wl_display *display, uint32_t version); + +#endif diff --git a/protocol/meson.build b/protocol/meson.build index d092687bc..d6a2eb1f5 100644 --- a/protocol/meson.build +++ b/protocol/meson.build @@ -42,6 +42,7 @@ protocols = { 'xdg-dialog-v1': wl_protocol_dir / 'staging/xdg-dialog/xdg-dialog-v1.xml', 'xdg-system-bell-v1': wl_protocol_dir / 'staging/xdg-system-bell/xdg-system-bell-v1.xml', 'xdg-toplevel-icon-v1': wl_protocol_dir / 'staging/xdg-toplevel-icon/xdg-toplevel-icon-v1.xml', + 'xdg-toplevel-tag-v1': wl_protocol_dir / 'staging/xdg-toplevel-tag/xdg-toplevel-tag-v1.xml', 'xwayland-shell-v1': wl_protocol_dir / 'staging/xwayland-shell/xwayland-shell-v1.xml', 'tearing-control-v1': wl_protocol_dir / 'staging/tearing-control/tearing-control-v1.xml', diff --git a/types/meson.build b/types/meson.build index 43c4eb2ad..0f664d0d8 100644 --- a/types/meson.build +++ b/types/meson.build @@ -103,6 +103,7 @@ wlr_files += files( 'wlr_xdg_output_v1.c', 'wlr_xdg_system_bell_v1.c', 'wlr_xdg_toplevel_icon_v1.c', + 'wlr_xdg_toplevel_tag_v1.c', ) if features.get('drm-backend') diff --git a/types/wlr_xdg_toplevel_tag_v1.c b/types/wlr_xdg_toplevel_tag_v1.c new file mode 100644 index 000000000..da8cfa9a0 --- /dev/null +++ b/types/wlr_xdg_toplevel_tag_v1.c @@ -0,0 +1,104 @@ +#include +#include + +#include +#include + +#include "xdg-toplevel-tag-v1-protocol.h" + +#define MANAGER_VERSION 1 + +static const struct xdg_toplevel_tag_manager_v1_interface manager_impl; + +static struct wlr_xdg_toplevel_tag_manager_v1 *manager_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, &xdg_toplevel_tag_manager_v1_interface, &manager_impl)); + return wl_resource_get_user_data(resource); +} + +static void manager_handle_set_tag(struct wl_client *client, struct wl_resource *manager_resource, + struct wl_resource *toplevel_resource, const char *tag) { + struct wlr_xdg_toplevel_tag_manager_v1 *manager = manager_from_resource(manager_resource); + struct wlr_xdg_toplevel *toplevel = wlr_xdg_toplevel_from_resource(toplevel_resource); + + struct wlr_xdg_toplevel_tag_manager_v1_set_tag_event event = { + .toplevel = toplevel, + .tag = tag, + }; + wl_signal_emit_mutable(&manager->events.set_tag, &event); +} + +static void manager_handle_set_description(struct wl_client *client, struct wl_resource *manager_resource, + struct wl_resource *toplevel_resource, const char *description) { + struct wlr_xdg_toplevel_tag_manager_v1 *manager = manager_from_resource(manager_resource); + struct wlr_xdg_toplevel *toplevel = wlr_xdg_toplevel_from_resource(toplevel_resource); + + struct wlr_xdg_toplevel_tag_manager_v1_set_description_event event = { + .toplevel = toplevel, + .description = description, + }; + wl_signal_emit_mutable(&manager->events.set_description, &event); +} + +static void manager_handle_destroy(struct wl_client *client, struct wl_resource *manager_resource) { + wl_resource_destroy(manager_resource); +} + +static const struct xdg_toplevel_tag_manager_v1_interface manager_impl = { + .destroy = manager_handle_destroy, + .set_toplevel_tag = manager_handle_set_tag, + .set_toplevel_description = manager_handle_set_description, +}; + +static void manager_bind(struct wl_client *client, void *data, uint32_t version, uint32_t id) { + struct wlr_xdg_toplevel_tag_manager_v1 *manager = data; + + struct wl_resource *resource = wl_resource_create(client, + &xdg_toplevel_tag_manager_v1_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &manager_impl, manager, NULL); +} + +static void manager_handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_xdg_toplevel_tag_manager_v1 *manager = + wl_container_of(listener, manager, display_destroy); + + wl_signal_emit_mutable(&manager->events.destroy, NULL); + + assert(wl_list_empty(&manager->events.set_tag.listener_list)); + assert(wl_list_empty(&manager->events.set_description.listener_list)); + assert(wl_list_empty(&manager->events.destroy.listener_list)); + + wl_list_remove(&manager->display_destroy.link); + wl_global_destroy(manager->global); + free(manager); +} + +struct wlr_xdg_toplevel_tag_manager_v1 *wlr_xdg_toplevel_tag_manager_v1_create( + struct wl_display *display, uint32_t version) { + assert(version <= MANAGER_VERSION); + + struct wlr_xdg_toplevel_tag_manager_v1 *manager = calloc(1, sizeof(*manager)); + if (manager == NULL) { + return NULL; + } + + manager->global = wl_global_create(display, &xdg_toplevel_tag_manager_v1_interface, + version, manager, manager_bind); + if (manager->global == NULL) { + free(manager); + return NULL; + } + + wl_signal_init(&manager->events.set_tag); + wl_signal_init(&manager->events.set_description); + wl_signal_init(&manager->events.destroy); + + manager->display_destroy.notify = manager_handle_display_destroy; + wl_display_add_destroy_listener(display, &manager->display_destroy); + + return manager; +} From c39b3ce7a3c4380d7a77f4cb97b3604c227c04d0 Mon Sep 17 00:00:00 2001 From: Consolatis <40171-Consolatis@users.noreply.gitlab.freedesktop.org> Date: Thu, 10 Jul 2025 19:46:17 +0200 Subject: [PATCH 075/311] transient_seat: initialize seat destroy listener This fixes a `wl_list_remove()` from an uninitialized listener when using `wlr_transient_seat_v1_deny()` in a `create_seat` handler. --- types/wlr_transient_seat_v1.c | 1 + 1 file changed, 1 insertion(+) diff --git a/types/wlr_transient_seat_v1.c b/types/wlr_transient_seat_v1.c index af8f87ccf..d99ee6edb 100644 --- a/types/wlr_transient_seat_v1.c +++ b/types/wlr_transient_seat_v1.c @@ -64,6 +64,7 @@ static void manager_create_transient_seat(struct wl_client *client, wl_resource_set_implementation(seat->resource, &transient_seat_impl, seat, transient_seat_handle_resource_destroy); + wl_list_init(&seat->seat_destroy.link); wl_signal_emit_mutable(&manager->events.create_seat, seat); return; From c14aa1d0b82b5e1853a800534f76b90bc55df392 Mon Sep 17 00:00:00 2001 From: YaoBing Xiao Date: Sun, 13 Jul 2025 23:31:00 +0800 Subject: [PATCH 076/311] render/vulkan: destroy vulkan instance when drm phdev mismatch --- render/vulkan/renderer.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/render/vulkan/renderer.c b/render/vulkan/renderer.c index a0e456580..266ac61cf 100644 --- a/render/vulkan/renderer.c +++ b/render/vulkan/renderer.c @@ -2514,14 +2514,13 @@ struct wlr_renderer *wlr_vk_renderer_create_with_drm_fd(int drm_fd) { if (!phdev) { // We rather fail here than doing some guesswork wlr_log(WLR_ERROR, "Could not match drm and vulkan device"); - return NULL; + goto error; } struct wlr_vk_device *dev = vulkan_device_create(ini, phdev); if (!dev) { wlr_log(WLR_ERROR, "Failed to create vulkan device"); - vulkan_instance_destroy(ini); - return NULL; + goto error; } // Do not use the drm_fd that was passed in: we should prefer the render @@ -2529,6 +2528,10 @@ struct wlr_renderer *wlr_vk_renderer_create_with_drm_fd(int drm_fd) { dev->drm_fd = vulkan_open_phdev_drm_fd(phdev); return vulkan_renderer_create_for_device(dev); + +error: + vulkan_instance_destroy(ini); + return NULL; } VkInstance wlr_vk_renderer_get_instance(struct wlr_renderer *renderer) { From f5dc6416f0f7142145fbe63913d4dcdeb1305149 Mon Sep 17 00:00:00 2001 From: David Turner Date: Tue, 15 Jul 2025 14:01:54 +0100 Subject: [PATCH 077/311] util/mem: Move memdup to new util/mem.c file This seems handy and I want to use this in wlr_color_representation. --- include/util/mem.h | 14 ++++++++++++++ types/wlr_color_management_v1.c | 13 ++----------- util/mem.c | 16 ++++++++++++++++ util/meson.build | 1 + 4 files changed, 33 insertions(+), 11 deletions(-) create mode 100644 include/util/mem.h create mode 100644 util/mem.c diff --git a/include/util/mem.h b/include/util/mem.h new file mode 100644 index 000000000..8971d2087 --- /dev/null +++ b/include/util/mem.h @@ -0,0 +1,14 @@ +#ifndef UTIL_MEM_H +#define UTIL_MEM_H + +#include +#include + +/** + * Allocate a new block of memory and copy *src to it, then store the address + * of the new allocation in *out. Returns true if it worked, or false if + * allocation failed. + */ +bool memdup(void *out, const void *src, size_t size); + +#endif // UTIL_MEM_H diff --git a/types/wlr_color_management_v1.c b/types/wlr_color_management_v1.c index b5e9a65fa..d9a3aba1b 100644 --- a/types/wlr_color_management_v1.c +++ b/types/wlr_color_management_v1.c @@ -1,12 +1,14 @@ #include #include #include + #include #include #include #include #include "render/color.h" +#include "util/mem.h" #define COLOR_MANAGEMENT_V1_VERSION 1 @@ -898,17 +900,6 @@ static void manager_handle_display_destroy(struct wl_listener *listener, void *d free(manager); } -static bool memdup(void *out, const void *src, size_t size) { - void *dst = malloc(size); - if (dst == NULL) { - return false; - } - memcpy(dst, src, size); - void **dst_ptr = out; - *dst_ptr = dst; - return true; -} - struct wlr_color_manager_v1 *wlr_color_manager_v1_create(struct wl_display *display, uint32_t version, const struct wlr_color_manager_v1_options *options) { assert(version <= COLOR_MANAGEMENT_V1_VERSION); diff --git a/util/mem.c b/util/mem.c new file mode 100644 index 000000000..cfd2f80e4 --- /dev/null +++ b/util/mem.c @@ -0,0 +1,16 @@ +#include +#include +#include + +#include "util/mem.h" + +bool memdup(void *out, const void *src, size_t size) { + void *dst = malloc(size); + if (dst == NULL) { + return false; + } + memcpy(dst, src, size); + void **dst_ptr = out; + *dst_ptr = dst; + return true; +} diff --git a/util/meson.build b/util/meson.build index fbdd54bbb..d67911e52 100644 --- a/util/meson.build +++ b/util/meson.build @@ -6,6 +6,7 @@ wlr_files += files( 'global.c', 'log.c', 'matrix.c', + 'mem.c', 'rect_union.c', 'region.c', 'set.c', From eff620770cca757b611e3b31812442e70dd7fbe7 Mon Sep 17 00:00:00 2001 From: David Turner Date: Tue, 4 Feb 2025 17:44:58 +0000 Subject: [PATCH 078/311] color-representation-v1: new protocol Add a minimal implementation of the color-representation-v1 protocol (not including anything to actually use the new properties in rendering/scanout) --- .../wlr/types/wlr_color_representation_v1.h | 85 +++++ protocol/meson.build | 1 + types/meson.build | 1 + types/wlr_color_representation_v1.c | 340 ++++++++++++++++++ 4 files changed, 427 insertions(+) create mode 100644 include/wlr/types/wlr_color_representation_v1.h create mode 100644 types/wlr_color_representation_v1.c diff --git a/include/wlr/types/wlr_color_representation_v1.h b/include/wlr/types/wlr_color_representation_v1.h new file mode 100644 index 000000000..251f4e6f9 --- /dev/null +++ b/include/wlr/types/wlr_color_representation_v1.h @@ -0,0 +1,85 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_COLOR_REPRESENTATION_V1_H +#define WLR_TYPES_WLR_COLOR_REPRESENTATION_V1_H + +#include +#include + +#include "color-representation-v1-protocol.h" + +struct wlr_surface; + +// Supported coefficients and range are always paired together +struct wlr_color_representation_v1_coeffs_and_range { + enum wp_color_representation_surface_v1_coefficients coeffs; + enum wp_color_representation_surface_v1_range range; +}; + +struct wlr_color_representation_manager_v1 { + struct wl_global *global; + + struct { + // Manager is being destroyed + struct wl_signal destroy; + } events; + + struct { + enum wp_color_representation_surface_v1_alpha_mode + *supported_alpha_modes; + size_t supported_alpha_modes_len; + + struct wlr_color_representation_v1_coeffs_and_range + *supported_coeffs_and_ranges; + size_t supported_coeffs_and_ranges_len; + + struct wl_listener display_destroy; + } WLR_PRIVATE; +}; + +// Options used when initialising a wlr_color_representation_manager_v1 +struct wlr_color_representation_v1_options { + enum wp_color_representation_surface_v1_alpha_mode + *supported_alpha_modes; + size_t supported_alpha_modes_len; + + const struct wlr_color_representation_v1_coeffs_and_range + *supported_coeffs_and_ranges; + size_t supported_coeffs_and_ranges_len; +}; + +struct wlr_color_representation_manager_v1 *wlr_color_representation_manager_v1_create( + struct wl_display *display, uint32_t version, + const struct wlr_color_representation_v1_options *options); + +// This is all the color-representation state which can be attached to a +// surface, double-buffered and made current on commit +struct wlr_color_representation_v1_surface_state { + // The enum premultiplied_electrical has value zero and is defined + // to be the default if unspecified. + enum wp_color_representation_surface_v1_alpha_mode alpha_mode; + + // If zero then indicates unset, otherwise values correspond to + // enum wp_color_representation_surface_v1_coefficients + uint32_t coefficients; + + // If zero then indicates unset, otherwise values correspond to + // enum wp_color_representation_surface_v1_range + uint32_t range; + + // If zero then indicates unset, otherwise values correspond to + // enum wp_color_representation_surface_v1_chroma_location + uint32_t chroma_location; +}; + +// Get the current color representation state committed to a surface +const struct wlr_color_representation_v1_surface_state *wlr_color_representation_v1_get_surface_state( + struct wlr_surface *surface); + +#endif // WLR_TYPES_WLR_COLOR_REPRESENTATION_V1_H diff --git a/protocol/meson.build b/protocol/meson.build index d6a2eb1f5..c8547933c 100644 --- a/protocol/meson.build +++ b/protocol/meson.build @@ -25,6 +25,7 @@ protocols = { # Staging upstream protocols 'alpha-modifier-v1': wl_protocol_dir / 'staging/alpha-modifier/alpha-modifier-v1.xml', 'color-management-v1': wl_protocol_dir / 'staging/color-management/color-management-v1.xml', + 'color-representation-v1': wl_protocol_dir / 'staging/color-representation/color-representation-v1.xml', 'content-type-v1': wl_protocol_dir / 'staging/content-type/content-type-v1.xml', 'cursor-shape-v1': wl_protocol_dir / 'staging/cursor-shape/cursor-shape-v1.xml', 'drm-lease-v1': wl_protocol_dir / 'staging/drm-lease/drm-lease-v1.xml', diff --git a/types/meson.build b/types/meson.build index 0f664d0d8..25a0d4434 100644 --- a/types/meson.build +++ b/types/meson.build @@ -48,6 +48,7 @@ wlr_files += files( 'wlr_drm.c', 'wlr_export_dmabuf_v1.c', 'wlr_foreign_toplevel_management_v1.c', + 'wlr_color_representation_v1.c', 'wlr_ext_image_copy_capture_v1.c', 'wlr_ext_foreign_toplevel_list_v1.c', 'wlr_ext_data_control_v1.c', diff --git a/types/wlr_color_representation_v1.c b/types/wlr_color_representation_v1.c new file mode 100644 index 000000000..294f946a0 --- /dev/null +++ b/types/wlr_color_representation_v1.c @@ -0,0 +1,340 @@ +#include +#include + +#include +#include +#include +#include + +#include "color-representation-v1-protocol.h" +#include "util/mem.h" + +#define WP_COLOR_REPRESENTATION_VERSION 1 + +struct wlr_color_representation_v1 { + struct wl_resource *resource; + struct wlr_surface *surface; + + struct wlr_color_representation_manager_v1 *manager; + + // Associate the wlr_color_representation_v1 with a wlr_surface + struct wlr_addon addon; + + struct wlr_surface_synced synced; + struct wlr_color_representation_v1_surface_state pending, current; +}; + +static const struct wp_color_representation_surface_v1_interface color_repr_impl; + +static struct wlr_color_representation_v1 *color_repr_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &wp_color_representation_surface_v1_interface, + &color_repr_impl)); + return wl_resource_get_user_data(resource); +} + +static void color_repr_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + // Actual destroying is done by the resource-destroy handler + wl_resource_destroy(resource); +} + +static void color_repr_handle_set_alpha_mode(struct wl_client *client, + struct wl_resource *resource, uint32_t alpha_mode) { + struct wlr_color_representation_v1 *color_repr = + color_repr_from_resource(resource); + if (color_repr == NULL) { + wl_resource_post_error(resource, WP_COLOR_REPRESENTATION_SURFACE_V1_ERROR_INERT, + "Associated surface has been destroyed, object is inert"); + return; + } + + bool found = false; + for (size_t i = 0; i < color_repr->manager->supported_alpha_modes_len; i++) { + if (color_repr->manager->supported_alpha_modes[i] == alpha_mode) { + found = true; + break; + } + } + if (!found) { + wl_resource_post_error(resource, + WP_COLOR_REPRESENTATION_SURFACE_V1_ERROR_ALPHA_MODE, + "Unsupported alpha mode"); + return; + } + + color_repr->pending.alpha_mode = alpha_mode; +} + +static void color_repr_handle_set_coefficients_and_range(struct wl_client *client, + struct wl_resource *resource, uint32_t coefficients, + uint32_t range) { + struct wlr_color_representation_v1 *color_repr = + color_repr_from_resource(resource); + if (color_repr == NULL) { + wl_resource_post_error(resource, WP_COLOR_REPRESENTATION_SURFACE_V1_ERROR_INERT, + "Associated surface has been destroyed, object is inert"); + return; + } + + bool found = false; + for (size_t i = 0; i < color_repr->manager->supported_coeffs_and_ranges_len; i++) { + struct wlr_color_representation_v1_coeffs_and_range *supported = + &color_repr->manager->supported_coeffs_and_ranges[i]; + if (supported->coeffs == coefficients && supported->range == range) { + found = true; + break; + } + } + if (!found) { + wl_resource_post_error(resource, + WP_COLOR_REPRESENTATION_SURFACE_V1_ERROR_COEFFICIENTS, + "Unsupported coefficients/range pair"); + return; + } + + color_repr->pending.coefficients = coefficients; + color_repr->pending.range = range; +} + +static void color_repr_handle_set_chroma_location(struct wl_client *client, + struct wl_resource *resource, uint32_t chroma_location) { + struct wlr_color_representation_v1 *color_repr = + color_repr_from_resource(resource); + if (color_repr == NULL) { + wl_resource_post_error(resource, WP_COLOR_REPRESENTATION_SURFACE_V1_ERROR_INERT, + "Associated surface has been destroyed, object is inert"); + return; + } + + uint32_t version = wl_resource_get_version(resource); + if (!wp_color_representation_surface_v1_chroma_location_is_valid( + version, chroma_location)) { + wlr_log(WLR_ERROR, "Client sent chroma location which isn't a valid enum value"); + // TODO: Post actual error once + // https://gitlab.freedesktop.org/wayland/wayland-protocols/-/merge_requests/429 + // is merged and wlroots depends on a new enough wayland-protocols. + wl_client_post_implementation_error(resource->client, + "Chroma location is not a valid enum value"); + return; + } + + // In this protocol there's no concept of supported chroma locations + // from a client point-of-view. The compositor should just ignore any + // chroma locations it doesn't know what to do with. + + color_repr->pending.chroma_location = chroma_location; +} + +static const struct wp_color_representation_surface_v1_interface color_repr_impl = { + .destroy = color_repr_handle_destroy, + .set_alpha_mode = color_repr_handle_set_alpha_mode, + .set_coefficients_and_range = color_repr_handle_set_coefficients_and_range, + .set_chroma_location = color_repr_handle_set_chroma_location, +}; + +static void color_repr_destroy(struct wlr_color_representation_v1 *color_repr) { + if (color_repr == NULL) { + return; + } + wlr_surface_synced_finish(&color_repr->synced); + wlr_addon_finish(&color_repr->addon); + wl_resource_set_user_data(color_repr->resource, NULL); + free(color_repr); +} + +static void color_repr_addon_destroy(struct wlr_addon *addon) { + struct wlr_color_representation_v1 *color_repr = + wl_container_of(addon, color_repr, addon); + color_repr_destroy(color_repr); +} + +static const struct wlr_addon_interface surface_addon_impl = { + .name = "wlr_color_representation_v1", + .destroy = color_repr_addon_destroy, +}; + +static void color_repr_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_color_representation_v1 *color_repr = + color_repr_from_resource(resource); + color_repr_destroy(color_repr); +} + +static void color_repr_manager_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + // Actual destroying is done by the resource-destroy handler + wl_resource_destroy(resource); +} + +static const struct wlr_surface_synced_impl surface_synced_impl = { + .state_size = sizeof(struct wlr_color_representation_v1_surface_state), +}; + +static struct wlr_color_representation_v1 *color_repr_from_surface( + struct wlr_surface *surface) { + struct wlr_addon *addon = wlr_addon_find(&surface->addons, NULL, &surface_addon_impl); + if (addon == NULL) { + return NULL; + } + struct wlr_color_representation_v1 *color_repr = wl_container_of(addon, color_repr, addon); + return color_repr; +} + +static const struct wp_color_representation_manager_v1_interface color_repr_manager_impl; + +static struct wlr_color_representation_manager_v1 *manager_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &wp_color_representation_manager_v1_interface, + &color_repr_manager_impl)); + return wl_resource_get_user_data(resource); +} + +static void color_repr_manager_handle_get_surface(struct wl_client *client, + struct wl_resource *manager_resource, + uint32_t color_repr_id, + struct wl_resource *surface_resource) { + struct wlr_surface *surface = wlr_surface_from_resource(surface_resource); + + // Check if there's already a color-representation attached to + // this surface + if (color_repr_from_surface(surface) != NULL) { + wl_resource_post_error(manager_resource, + WP_COLOR_REPRESENTATION_MANAGER_V1_ERROR_SURFACE_EXISTS, + "wp_color_representation_surface_v1 already exists for this surface"); + return; + } + + struct wlr_color_representation_v1 *color_repr = calloc(1, sizeof(*color_repr)); + if (!color_repr) { + wl_resource_post_no_memory(manager_resource); + return; + } + + color_repr->manager = manager_from_resource(manager_resource); + + if (!wlr_surface_synced_init(&color_repr->synced, surface, + &surface_synced_impl, &color_repr->pending, &color_repr->current)) { + free(color_repr); + wl_resource_post_no_memory(manager_resource); + return; + } + + uint32_t version = wl_resource_get_version(manager_resource); + color_repr->resource = wl_resource_create(client, + &wp_color_representation_surface_v1_interface, version, color_repr_id); + if (color_repr->resource == NULL) { + wlr_surface_synced_finish(&color_repr->synced); + free(color_repr); + wl_resource_post_no_memory(manager_resource); + return; + } + wl_resource_set_implementation(color_repr->resource, + &color_repr_impl, color_repr, color_repr_handle_resource_destroy); + + wlr_addon_init(&color_repr->addon, &surface->addons, NULL, &surface_addon_impl); +} + +static const struct wp_color_representation_manager_v1_interface color_repr_manager_impl = { + .destroy = color_repr_manager_handle_destroy, + .get_surface = color_repr_manager_handle_get_surface, +}; + +static void send_supported(struct wlr_color_representation_manager_v1 *manager, + struct wl_resource *resource) { + for (size_t i = 0; i < manager->supported_alpha_modes_len; i++) { + wp_color_representation_manager_v1_send_supported_alpha_mode( + resource, manager->supported_alpha_modes[i]); + } + + for (size_t i = 0; i < manager->supported_coeffs_and_ranges_len; i++) { + struct wlr_color_representation_v1_coeffs_and_range *supported = + &manager->supported_coeffs_and_ranges[i]; + wp_color_representation_manager_v1_send_supported_coefficients_and_ranges( + resource, supported->coeffs, supported->range); + } + + // Note that there is no event for supported chroma locations in the + // v1 protocol. + + wp_color_representation_manager_v1_send_done(resource); +} + +static void manager_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) { + struct wlr_color_representation_manager_v1 *manager = data; + + struct wl_resource *resource = wl_resource_create(client, + &wp_color_representation_manager_v1_interface, + version, id); + if (resource == NULL) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &color_repr_manager_impl, manager, NULL); + + send_supported(manager, resource); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_color_representation_manager_v1 *manager = + wl_container_of(listener, manager, display_destroy); + + wl_signal_emit_mutable(&manager->events.destroy, NULL); + + assert(wl_list_empty(&manager->events.destroy.listener_list)); + + wl_list_remove(&manager->display_destroy.link); + wl_global_destroy(manager->global); + free(manager); +} + +struct wlr_color_representation_manager_v1 *wlr_color_representation_manager_v1_create( + struct wl_display *display, uint32_t version, + const struct wlr_color_representation_v1_options *options) { + assert(version <= WP_COLOR_REPRESENTATION_VERSION); + + struct wlr_color_representation_manager_v1 *manager = calloc(1, sizeof(*manager)); + if (manager == NULL) { + return NULL; + } + + manager->global = wl_global_create(display, + &wp_color_representation_manager_v1_interface, + version, manager, manager_bind); + if (manager->global == NULL) { + free(manager); + return NULL; + } + + bool ok = true; + ok &= memdup(&manager->supported_alpha_modes, + options->supported_alpha_modes, + sizeof(options->supported_alpha_modes[0]) * options->supported_alpha_modes_len); + ok &= memdup(&manager->supported_coeffs_and_ranges, + options->supported_coeffs_and_ranges, + sizeof(options->supported_coeffs_and_ranges[0]) * options->supported_coeffs_and_ranges_len); + if (!ok) { + goto err_options; + } + + manager->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &manager->display_destroy); + + return manager; + +err_options: + free(manager->supported_alpha_modes); + free(manager->supported_coeffs_and_ranges); + return NULL; +} + +const struct wlr_color_representation_v1_surface_state *wlr_color_representation_v1_get_surface_state( + struct wlr_surface *surface) { + struct wlr_color_representation_v1 *color_repr = color_repr_from_surface(surface); + if (color_repr == NULL) { + return NULL; + } + return &color_repr->current; +} From a4eb2cff46a9b07582873f26d97ddab9abc7fd92 Mon Sep 17 00:00:00 2001 From: David Turner Date: Thu, 17 Jul 2025 10:55:11 +0100 Subject: [PATCH 079/311] color-representation-v1: Add wlr enums + converters Add wlr-internal enums for the properties specified in color-representation-v1 (encoding, range, chroma siting, alpha mode) so that other parts of wlroots can use these without depending on the protocol specifics and without needing to include the protocol headers. Also add conversion functions to convert the protocol enum values into the wlroots enum values. --- include/wlr/render/color.h | 51 ++++++++++++++ .../wlr/types/wlr_color_representation_v1.h | 9 +++ types/wlr_color_representation_v1.c | 66 +++++++++++++++++++ 3 files changed, 126 insertions(+) diff --git a/include/wlr/render/color.h b/include/wlr/render/color.h index 3341668a8..e2397b75a 100644 --- a/include/wlr/render/color.h +++ b/include/wlr/render/color.h @@ -30,6 +30,57 @@ enum wlr_color_transfer_function { WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR = 1 << 2, }; +/** + * Specifies alpha blending modes. Note that premultiplied_electrical + * is the default, so there is no "none" or "unset" value. + */ +enum wlr_alpha_mode { + WLR_COLOR_ALPHA_MODE_PREMULTIPLIED_ELECTRICAL, + WLR_COLOR_ALPHA_MODE_PREMULTIPLIED_OPTICAL, + WLR_COLOR_ALPHA_MODE_STRAIGHT, +}; + +/** + * Well-known color encodings, each representing a set of matrix coefficients + * used to convert that particular YCbCr encoding to RGB. NONE means the + * value is unset or unknown. + */ +enum wlr_color_encoding { + WLR_COLOR_ENCODING_NONE, + WLR_COLOR_ENCODING_IDENTITY, + WLR_COLOR_ENCODING_BT709, + WLR_COLOR_ENCODING_FCC, + WLR_COLOR_ENCODING_BT601, + WLR_COLOR_ENCODING_SMPTE240, + WLR_COLOR_ENCODING_BT2020, + WLR_COLOR_ENCODING_BT2020_CL, + WLR_COLOR_ENCODING_ICTCP, +}; + +/** + * Specifies whether a particular color-encoding uses full- or limited-range + * values. NONE means the value is unset or unknown. + */ +enum wlr_color_range { + WLR_COLOR_RANGE_NONE, + WLR_COLOR_RANGE_LIMITED, + WLR_COLOR_RANGE_FULL, +}; + +/** + * Chroma sample locations, corresponding to Chroma420SampleLocType code + * points in H.273. NONE means the value is unset or unknown. + */ +enum wlr_color_chroma_location { + WLR_COLOR_CHROMA_LOCATION_NONE, + WLR_COLOR_CHROMA_LOCATION_TYPE0, + WLR_COLOR_CHROMA_LOCATION_TYPE1, + WLR_COLOR_CHROMA_LOCATION_TYPE2, + WLR_COLOR_CHROMA_LOCATION_TYPE3, + WLR_COLOR_CHROMA_LOCATION_TYPE4, + WLR_COLOR_CHROMA_LOCATION_TYPE5, +}; + /** * CIE 1931 xy chromaticity coordinates. */ diff --git a/include/wlr/types/wlr_color_representation_v1.h b/include/wlr/types/wlr_color_representation_v1.h index 251f4e6f9..d575eedf5 100644 --- a/include/wlr/types/wlr_color_representation_v1.h +++ b/include/wlr/types/wlr_color_representation_v1.h @@ -82,4 +82,13 @@ struct wlr_color_representation_v1_surface_state { const struct wlr_color_representation_v1_surface_state *wlr_color_representation_v1_get_surface_state( struct wlr_surface *surface); +enum wlr_alpha_mode wlr_color_representation_v1_alpha_mode_to_wlr( + enum wp_color_representation_surface_v1_alpha_mode wp_val); +enum wlr_color_encoding wlr_color_representation_v1_color_encoding_to_wlr( + enum wp_color_representation_surface_v1_coefficients wp_val); +enum wlr_color_range wlr_color_representation_v1_color_range_to_wlr( + enum wp_color_representation_surface_v1_range wp_val); +enum wlr_color_chroma_location wlr_color_representation_v1_chroma_location_to_wlr( + enum wp_color_representation_surface_v1_chroma_location wp_val); + #endif // WLR_TYPES_WLR_COLOR_REPRESENTATION_V1_H diff --git a/types/wlr_color_representation_v1.c b/types/wlr_color_representation_v1.c index 294f946a0..0bf33d156 100644 --- a/types/wlr_color_representation_v1.c +++ b/types/wlr_color_representation_v1.c @@ -11,6 +11,72 @@ #define WP_COLOR_REPRESENTATION_VERSION 1 +enum wlr_alpha_mode wlr_color_representation_v1_alpha_mode_to_wlr( + enum wp_color_representation_surface_v1_alpha_mode wp_val) { + switch (wp_val) { + case WP_COLOR_REPRESENTATION_SURFACE_V1_ALPHA_MODE_PREMULTIPLIED_ELECTRICAL: + return WLR_COLOR_ALPHA_MODE_PREMULTIPLIED_ELECTRICAL; + case WP_COLOR_REPRESENTATION_SURFACE_V1_ALPHA_MODE_PREMULTIPLIED_OPTICAL: + return WLR_COLOR_ALPHA_MODE_PREMULTIPLIED_OPTICAL; + case WP_COLOR_REPRESENTATION_SURFACE_V1_ALPHA_MODE_STRAIGHT: + return WLR_COLOR_ALPHA_MODE_STRAIGHT; + } + abort(); // unreachable +} + +enum wlr_color_encoding wlr_color_representation_v1_color_encoding_to_wlr( + enum wp_color_representation_surface_v1_coefficients wp_val) { + switch (wp_val) { + case WP_COLOR_REPRESENTATION_SURFACE_V1_COEFFICIENTS_IDENTITY: + return WLR_COLOR_ENCODING_IDENTITY; + case WP_COLOR_REPRESENTATION_SURFACE_V1_COEFFICIENTS_BT709: + return WLR_COLOR_ENCODING_BT709; + case WP_COLOR_REPRESENTATION_SURFACE_V1_COEFFICIENTS_FCC: + return WLR_COLOR_ENCODING_FCC; + case WP_COLOR_REPRESENTATION_SURFACE_V1_COEFFICIENTS_BT601: + return WLR_COLOR_ENCODING_BT601; + case WP_COLOR_REPRESENTATION_SURFACE_V1_COEFFICIENTS_SMPTE240: + return WLR_COLOR_ENCODING_SMPTE240; + case WP_COLOR_REPRESENTATION_SURFACE_V1_COEFFICIENTS_BT2020: + return WLR_COLOR_ENCODING_BT2020; + case WP_COLOR_REPRESENTATION_SURFACE_V1_COEFFICIENTS_BT2020_CL: + return WLR_COLOR_ENCODING_BT2020_CL; + case WP_COLOR_REPRESENTATION_SURFACE_V1_COEFFICIENTS_ICTCP: + return WLR_COLOR_ENCODING_ICTCP; + } + abort(); // unreachable +} + +enum wlr_color_range wlr_color_representation_v1_color_range_to_wlr( + enum wp_color_representation_surface_v1_range wp_val) { + switch (wp_val) { + case WP_COLOR_REPRESENTATION_SURFACE_V1_RANGE_LIMITED: + return WLR_COLOR_RANGE_LIMITED; + case WP_COLOR_REPRESENTATION_SURFACE_V1_RANGE_FULL: + return WLR_COLOR_RANGE_FULL; + } + abort(); // unreachable +} + +enum wlr_color_chroma_location wlr_color_representation_v1_chroma_location_to_wlr( + enum wp_color_representation_surface_v1_chroma_location wp_val) { + switch (wp_val) { + case WP_COLOR_REPRESENTATION_SURFACE_V1_CHROMA_LOCATION_TYPE_0: + return WLR_COLOR_CHROMA_LOCATION_TYPE0; + case WP_COLOR_REPRESENTATION_SURFACE_V1_CHROMA_LOCATION_TYPE_1: + return WLR_COLOR_CHROMA_LOCATION_TYPE1; + case WP_COLOR_REPRESENTATION_SURFACE_V1_CHROMA_LOCATION_TYPE_2: + return WLR_COLOR_CHROMA_LOCATION_TYPE2; + case WP_COLOR_REPRESENTATION_SURFACE_V1_CHROMA_LOCATION_TYPE_3: + return WLR_COLOR_CHROMA_LOCATION_TYPE3; + case WP_COLOR_REPRESENTATION_SURFACE_V1_CHROMA_LOCATION_TYPE_4: + return WLR_COLOR_CHROMA_LOCATION_TYPE4; + case WP_COLOR_REPRESENTATION_SURFACE_V1_CHROMA_LOCATION_TYPE_5: + return WLR_COLOR_CHROMA_LOCATION_TYPE5; + } + abort(); // unreachable +} + struct wlr_color_representation_v1 { struct wl_resource *resource; struct wlr_surface *surface; From d2007d7dc1ffb38a24922a7842c7cebbdbab46ee Mon Sep 17 00:00:00 2001 From: Yixue Wang Date: Fri, 18 Jul 2025 17:56:22 +0800 Subject: [PATCH 080/311] types/color_representation: correctly cleanup in manager create Global should be created after all other initialization finished. Free manager in err_options. --- types/wlr_color_representation_v1.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/types/wlr_color_representation_v1.c b/types/wlr_color_representation_v1.c index 0bf33d156..dbf2ecd52 100644 --- a/types/wlr_color_representation_v1.c +++ b/types/wlr_color_representation_v1.c @@ -366,14 +366,6 @@ struct wlr_color_representation_manager_v1 *wlr_color_representation_manager_v1_ return NULL; } - manager->global = wl_global_create(display, - &wp_color_representation_manager_v1_interface, - version, manager, manager_bind); - if (manager->global == NULL) { - free(manager); - return NULL; - } - bool ok = true; ok &= memdup(&manager->supported_alpha_modes, options->supported_alpha_modes, @@ -385,6 +377,13 @@ struct wlr_color_representation_manager_v1 *wlr_color_representation_manager_v1_ goto err_options; } + manager->global = wl_global_create(display, + &wp_color_representation_manager_v1_interface, + version, manager, manager_bind); + if (manager->global == NULL) { + goto err_options; + } + manager->display_destroy.notify = handle_display_destroy; wl_display_add_destroy_listener(display, &manager->display_destroy); @@ -393,6 +392,7 @@ struct wlr_color_representation_manager_v1 *wlr_color_representation_manager_v1_ err_options: free(manager->supported_alpha_modes); free(manager->supported_coeffs_and_ranges); + free(manager); return NULL; } From ccec4116b3a113e9a082f9712f675c30552a0b35 Mon Sep 17 00:00:00 2001 From: Yixue Wang Date: Sun, 20 Jul 2025 23:01:58 +0800 Subject: [PATCH 081/311] types/color_management: check on invalid image description Check if image description is valid. If not, post error to client. --- types/wlr_color_management_v1.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/types/wlr_color_management_v1.c b/types/wlr_color_management_v1.c index d9a3aba1b..fe0994e0b 100644 --- a/types/wlr_color_management_v1.c +++ b/types/wlr_color_management_v1.c @@ -310,6 +310,13 @@ static void cm_surface_handle_set_image_description(struct wl_client *client, struct wlr_image_description_v1 *image_desc = image_desc_from_resource(image_desc_resource); + if (image_desc == NULL) { + wl_resource_post_error(cm_surface_resource, + WP_COLOR_MANAGEMENT_SURFACE_V1_ERROR_IMAGE_DESCRIPTION, + "Image description to be set is invalid"); + return; + } + bool found = false; for (size_t i = 0; i < cm_surface->manager->render_intents_len; i++) { if (cm_surface->manager->render_intents[i] == render_intent) { From 80c7e0f772e38f56376e7953b148b480ca49ef3a Mon Sep 17 00:00:00 2001 From: Andri Yngvason Date: Wed, 23 Jul 2025 15:05:54 +0000 Subject: [PATCH 082/311] ext-image-capture-source: output: Apply transform to cursor The cursor can be expected to also be transformed if the output is transformed. --- types/ext_image_capture_source_v1/output.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/types/ext_image_capture_source_v1/output.c b/types/ext_image_capture_source_v1/output.c index a2726c759..1112b64d5 100644 --- a/types/ext_image_capture_source_v1/output.c +++ b/types/ext_image_capture_source_v1/output.c @@ -283,7 +283,8 @@ static void output_cursor_source_copy_frame(struct wlr_ext_image_capture_source_ struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); - wlr_ext_image_copy_capture_frame_v1_ready(frame, WL_OUTPUT_TRANSFORM_NORMAL, &now); + wlr_ext_image_copy_capture_frame_v1_ready(frame, + cursor_source->output->transform, &now); } static const struct wlr_ext_image_capture_source_v1_interface output_cursor_source_impl = { From be5e2662113ca40a21f48a5af35a219f50f28794 Mon Sep 17 00:00:00 2001 From: liupeng Date: Wed, 23 Jul 2025 10:53:42 +0800 Subject: [PATCH 083/311] cursor: update output cursor even if output is disabled During suspend, we first disable output and then remove the input device. This causes cursor->state->surface released while cursor->texture leaves. Which leads to use-after-free after resume. --- types/wlr_cursor.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/types/wlr_cursor.c b/types/wlr_cursor.c index 7f99fef0c..6dad11446 100644 --- a/types/wlr_cursor.c +++ b/types/wlr_cursor.c @@ -530,10 +530,6 @@ static void cursor_output_cursor_update(struct wlr_cursor_output_cursor *output_ struct wlr_cursor *cur = output_cursor->cursor; struct wlr_output *output = output_cursor->output_cursor->output; - if (!output->enabled) { - return; - } - cursor_output_cursor_reset_image(output_cursor); if (cur->state->buffer != NULL) { From efb17980a84f88098371d3f258e6eddcebe021b7 Mon Sep 17 00:00:00 2001 From: rewine Date: Mon, 21 Jul 2025 09:49:08 +0800 Subject: [PATCH 084/311] ext_image_capture_source_v1: remove unused struct definition Remove the redundant wlr_ext_foreign_toplevel_image_capture_source_v1 struct that was not used anywhere in the codebase. --- types/ext_image_capture_source_v1/foreign_toplevel.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/types/ext_image_capture_source_v1/foreign_toplevel.c b/types/ext_image_capture_source_v1/foreign_toplevel.c index c79156bce..e5c75e280 100644 --- a/types/ext_image_capture_source_v1/foreign_toplevel.c +++ b/types/ext_image_capture_source_v1/foreign_toplevel.c @@ -6,10 +6,6 @@ #define FOREIGN_TOPLEVEL_IMAGE_SOURCE_MANAGER_V1_VERSION 1 -struct wlr_ext_foreign_toplevel_image_capture_source_v1 { - struct wlr_ext_image_capture_source_v1 base; -}; - static const struct ext_foreign_toplevel_image_capture_source_manager_v1_interface foreign_toplevel_manager_impl; static struct wlr_ext_foreign_toplevel_image_capture_source_manager_v1 * From db5e9ca04ce516d812c8af13672d6894c4341a3e Mon Sep 17 00:00:00 2001 From: llyyr Date: Mon, 21 Jul 2025 23:12:00 +0530 Subject: [PATCH 085/311] meson: bump minimum wayland-protocols version color-representation was added in 1.44. Fixes: eff620770cca757b611e3b31812442e70dd7fbe7 --- protocol/meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocol/meson.build b/protocol/meson.build index c8547933c..5012753b5 100644 --- a/protocol/meson.build +++ b/protocol/meson.build @@ -1,5 +1,5 @@ wayland_protos = dependency('wayland-protocols', - version: '>=1.43', + version: '>=1.44', fallback: 'wayland-protocols', default_options: ['tests=false'], ) From 47a90d6f1a9b45f68f5ebfb5dcc1baac2476d15e Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Wed, 18 Jun 2025 22:43:24 +0200 Subject: [PATCH 086/311] color_management_v1: add helpers to convert TF/primaries enums This makes it easier for protocol implementers to tie everything together with wlroots backends and renderers. --- include/wlr/types/wlr_color_management_v1.h | 14 +++++++++++ types/wlr_color_management_v1.c | 26 +++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/include/wlr/types/wlr_color_management_v1.h b/include/wlr/types/wlr_color_management_v1.h index a369806bc..f369fb22c 100644 --- a/include/wlr/types/wlr_color_management_v1.h +++ b/include/wlr/types/wlr_color_management_v1.h @@ -89,4 +89,18 @@ void wlr_color_manager_v1_set_surface_preferred_image_description( struct wlr_color_manager_v1 *manager, struct wlr_surface *surface, const struct wlr_image_description_v1_data *data); +/** + * Convert a protocol transfer function to enum wlr_color_transfer_function. + * Aborts if there is no matching wlroots entry. + */ +enum wlr_color_transfer_function +wlr_color_manager_v1_transfer_function_to_wlr(enum wp_color_manager_v1_transfer_function tf); + +/** + * Convert a protocol named primaries to enum wlr_color_named_primaries. + * Aborts if there is no matching wlroots entry. + */ +enum wlr_color_named_primaries +wlr_color_manager_v1_primaries_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 fe0994e0b..b84781f6e 100644 --- a/types/wlr_color_management_v1.c +++ b/types/wlr_color_management_v1.c @@ -992,3 +992,29 @@ 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) { + switch (tf) { + case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_SRGB: + return WLR_COLOR_TRANSFER_FUNCTION_SRGB; + case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST2084_PQ: + return WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ; + case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR: + return WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR; + default: + abort(); + } +} + +enum wlr_color_named_primaries +wlr_color_manager_v1_primaries_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(); + } +} From 2f2c0dfcc63e68915eaf01e5a89408ecc2c9443b Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Wed, 18 Jun 2025 22:45:04 +0200 Subject: [PATCH 087/311] scene: use helpers to convert TF/primaries enums --- types/scene/surface.c | 26 ++------------------------ 1 file changed, 2 insertions(+), 24 deletions(-) diff --git a/types/scene/surface.c b/types/scene/surface.c index 1ee0e3134..7653323e3 100644 --- a/types/scene/surface.c +++ b/types/scene/surface.c @@ -195,30 +195,8 @@ static void surface_reconfigure(struct wlr_scene_surface *scene_surface) { const struct wlr_image_description_v1_data *img_desc = wlr_surface_get_image_description_v1_data(surface); if (img_desc != NULL) { - switch (img_desc->tf_named) { - case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_SRGB: - tf = WLR_COLOR_TRANSFER_FUNCTION_SRGB; - break; - case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST2084_PQ: - tf = WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ; - break; - case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR: - tf = WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR; - break; - default: - abort(); - } - - switch (img_desc->primaries_named) { - case WP_COLOR_MANAGER_V1_PRIMARIES_SRGB: - primaries = WLR_COLOR_NAMED_PRIMARIES_SRGB; - break; - case WP_COLOR_MANAGER_V1_PRIMARIES_BT2020: - primaries = WLR_COLOR_NAMED_PRIMARIES_BT2020; - break; - default: - abort(); - } + tf = wlr_color_manager_v1_transfer_function_to_wlr(img_desc->tf_named); + primaries = wlr_color_manager_v1_primaries_to_wlr(img_desc->primaries_named); } wlr_scene_buffer_set_opaque_region(scene_buffer, &opaque); From 1beb25a1c8fb5bd1485a552b61424e9d416bce8e Mon Sep 17 00:00:00 2001 From: qaqland Date: Tue, 22 Jul 2025 23:49:00 +0800 Subject: [PATCH 088/311] tinywl: fix cursor disappears when focused window is closed Add listener for wlr_seat->pointer_state.events.focus_change Fix #3802 --- tinywl/tinywl.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tinywl/tinywl.c b/tinywl/tinywl.c index 89837b4fb..b3d902c7e 100644 --- a/tinywl/tinywl.c +++ b/tinywl/tinywl.c @@ -56,6 +56,7 @@ struct tinywl_server { struct wlr_seat *seat; struct wl_listener new_input; struct wl_listener request_cursor; + struct wl_listener pointer_focus_change; struct wl_listener request_set_selection; struct wl_list keyboards; enum tinywl_cursor_mode cursor_mode; @@ -333,6 +334,18 @@ static void seat_request_cursor(struct wl_listener *listener, void *data) { } } +static void seat_pointer_focus_change(struct wl_listener *listener, void *data) { + struct tinywl_server *server = wl_container_of( + listener, server, pointer_focus_change); + /* This event is raised when the pointer focus is changed, including when the + * client is closed. We set the cursor image to its default if target surface + * is NULL */ + struct wlr_seat_pointer_focus_change_event *event = data; + if (event->new_surface == NULL) { + wlr_cursor_set_xcursor(server->cursor, server->cursor_mgr, "default"); + } +} + static void seat_request_set_selection(struct wl_listener *listener, void *data) { /* This event is raised by the seat when a client wants to set the selection, * usually when the user copies something. wlroots allows compositors to @@ -1018,6 +1031,9 @@ int main(int argc, char *argv[]) { server.request_cursor.notify = seat_request_cursor; wl_signal_add(&server.seat->events.request_set_cursor, &server.request_cursor); + server.pointer_focus_change.notify = seat_pointer_focus_change; + wl_signal_add(&server.seat->pointer_state.events.focus_change, + &server.pointer_focus_change); server.request_set_selection.notify = seat_request_set_selection; wl_signal_add(&server.seat->events.request_set_selection, &server.request_set_selection); @@ -1069,6 +1085,7 @@ int main(int argc, char *argv[]) { wl_list_remove(&server.new_input.link); wl_list_remove(&server.request_cursor.link); + wl_list_remove(&server.pointer_focus_change.link); wl_list_remove(&server.request_set_selection.link); wl_list_remove(&server.new_output.link); From 51a78cb0ed30fb9193e940e77f25bdf13131fcee Mon Sep 17 00:00:00 2001 From: Christopher Snowhill Date: Sun, 27 Jul 2025 01:32:24 -0700 Subject: [PATCH 089/311] color_management_v1: set output color properties This reports the output properties according to the current image description. Firefox needs this to report HDR support to documents, at least. v2: Move abort() calls out of switch to eliminate default case. Rename functions so they don't use a wlr_ prefix like public functions do. Signed-off-by: Christopher Snowhill --- types/wlr_color_management_v1.c | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/types/wlr_color_management_v1.c b/types/wlr_color_management_v1.c index b84781f6e..3af0b51e8 100644 --- a/types/wlr_color_management_v1.c +++ b/types/wlr_color_management_v1.c @@ -76,6 +76,17 @@ static enum wlr_color_named_primaries named_primaries_to_wlr( } } +static enum wp_color_manager_v1_primaries named_primaries_from_wlr( + enum wlr_color_named_primaries primaries) { + switch (primaries) { + case WLR_COLOR_NAMED_PRIMARIES_SRGB: + return WP_COLOR_MANAGER_V1_PRIMARIES_SRGB; + case WLR_COLOR_NAMED_PRIMARIES_BT2020: + return WP_COLOR_MANAGER_V1_PRIMARIES_BT2020; + } + abort(); +} + static enum wlr_color_transfer_function transfer_function_to_wlr( enum wp_color_manager_v1_transfer_function tf) { switch (tf) { @@ -90,6 +101,19 @@ static enum wlr_color_transfer_function transfer_function_to_wlr( } } +static enum wp_color_manager_v1_transfer_function transfer_function_from_wlr( + enum wlr_color_transfer_function tf) { + switch (tf) { + case WLR_COLOR_TRANSFER_FUNCTION_SRGB: + return WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_SRGB; + case WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ: + return WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST2084_PQ; + case WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR: + return WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR; + } + abort(); +} + static int32_t encode_cie1931_coord(float value) { return round(value * 1000 * 1000); } @@ -238,6 +262,11 @@ static void cm_output_handle_get_image_description(struct wl_client *client, .tf_named = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_SRGB, .primaries_named = WP_COLOR_MANAGER_V1_PRIMARIES_SRGB, }; + const struct wlr_output_image_description *image_desc = cm_output->output->image_description; + if (image_desc != NULL) { + data.tf_named = transfer_function_from_wlr(image_desc->transfer_function); + data.primaries_named = named_primaries_from_wlr(image_desc->primaries); + } image_desc_create_ready(cm_output->manager, cm_output_resource, id, &data, true); } @@ -691,6 +720,7 @@ static void manager_handle_get_output(struct wl_client *client, } cm_output->manager = manager; + cm_output->output = output; uint32_t version = wl_resource_get_version(manager_resource); cm_output->resource = wl_resource_create(client, From c8b7600adc35a31fa86be1bebf1c9bf79246f08a Mon Sep 17 00:00:00 2001 From: rewine Date: Wed, 30 Jul 2025 18:36:07 +0800 Subject: [PATCH 090/311] wlr_ext_data_control_v1: Make all listeners private For more context, see: https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/4873 --- include/wlr/types/wlr_ext_data_control_v1.h | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/include/wlr/types/wlr_ext_data_control_v1.h b/include/wlr/types/wlr_ext_data_control_v1.h index e49dfb3f5..7cdc7e95b 100644 --- a/include/wlr/types/wlr_ext_data_control_v1.h +++ b/include/wlr/types/wlr_ext_data_control_v1.h @@ -21,7 +21,9 @@ struct wlr_ext_data_control_manager_v1 { struct wl_signal new_device; // wlr_ext_data_control_device_v1 } events; - struct wl_listener display_destroy; + struct { + struct wl_listener display_destroy; + } WLR_PRIVATE; }; struct wlr_ext_data_control_device_v1 { @@ -33,9 +35,11 @@ struct wlr_ext_data_control_device_v1 { struct wl_resource *selection_offer_resource; // current selection offer struct wl_resource *primary_selection_offer_resource; // current primary selection offer - struct wl_listener seat_destroy; - struct wl_listener seat_set_selection; - struct wl_listener seat_set_primary_selection; + struct { + struct wl_listener seat_destroy; + struct wl_listener seat_set_selection; + struct wl_listener seat_set_primary_selection; + } WLR_PRIVATE; }; struct wlr_ext_data_control_manager_v1 *wlr_ext_data_control_manager_v1_create( From dd3c63f5e6b411ed59efeb55f88f67cc6242a70a Mon Sep 17 00:00:00 2001 From: Christopher Snowhill Date: Thu, 31 Jul 2025 06:46:47 -0700 Subject: [PATCH 091/311] color-representation-v1: Fix missing destroy signal init Fixes #4001 Reported-by: CreeperFace / @dy-tea Signed-off-by: Christopher Snowhill --- types/wlr_color_representation_v1.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/types/wlr_color_representation_v1.c b/types/wlr_color_representation_v1.c index dbf2ecd52..ac804c60a 100644 --- a/types/wlr_color_representation_v1.c +++ b/types/wlr_color_representation_v1.c @@ -384,6 +384,8 @@ struct wlr_color_representation_manager_v1 *wlr_color_representation_manager_v1_ goto err_options; } + wl_signal_init(&manager->events.destroy); + manager->display_destroy.notify = handle_display_destroy; wl_display_add_destroy_listener(display, &manager->display_destroy); From 12316417b033465114705430105785e72dfe345d Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Thu, 31 Jul 2025 15:21:28 +0200 Subject: [PATCH 092/311] ext_image_capture_source_v1: advertise fallback {A,X}RGB8888 formats We can't expect all clients to support all fancy formats. WebRTC's reference implementation doesn't support 10-bit formats yet. More generally, clients are limited by the libraries they use: for instance, Pixman doesn't implement all OpenGL/Vulkan formats. Another MR [1] suggests advertising all render formats. This is a bit heavy-handed because: - Upgrading a 8-bit buffer to a 10-bit buffer doesn't make a lot of sense. I don't think the compositor should expose arbitrary pixel format conversions. - The protocol has no preference order. Clients generally pick the first format they receive and support. As an alternative, only advertise two fallback formats, ARGB8888 and XRGB8888. These two are already hard-required by wl_shm and all clients should be able to handle them. [1]: https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/5019 --- types/ext_image_capture_source_v1/base.c | 25 +++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/types/ext_image_capture_source_v1/base.c b/types/ext_image_capture_source_v1/base.c index 7b34030c8..eccbb2884 100644 --- a/types/ext_image_capture_source_v1/base.c +++ b/types/ext_image_capture_source_v1/base.c @@ -10,6 +10,7 @@ #include #include #include "ext-image-capture-source-v1-protocol.h" +#include "render/wlr_renderer.h" static void source_handle_destroy(struct wl_client *client, struct wl_resource *source_resource) { @@ -96,6 +97,12 @@ static uint32_t get_swapchain_shm_format(struct wlr_swapchain *swapchain, return format; } +static void add_drm_format(struct wlr_drm_format_set *set, const struct wlr_drm_format *fmt) { + for (size_t i = 0; i < fmt->len; i++) { + wlr_drm_format_set_add(set, fmt->format, fmt->modifiers[i]); + } +} + bool wlr_ext_image_capture_source_v1_set_constraints_from_swapchain(struct wlr_ext_image_capture_source_v1 *source, struct wlr_swapchain *swapchain, struct wlr_renderer *renderer) { source->width = swapchain->width; @@ -130,9 +137,21 @@ bool wlr_ext_image_capture_source_v1_set_constraints_from_swapchain(struct wlr_e wlr_drm_format_set_finish(&source->dmabuf_formats); source->dmabuf_formats = (struct wlr_drm_format_set){0}; - for (size_t i = 0; i < swapchain->format.len; i++) { - wlr_drm_format_set_add(&source->dmabuf_formats, - swapchain->format.format, swapchain->format.modifiers[i]); + add_drm_format(&source->dmabuf_formats, &swapchain->format); + + const struct wlr_drm_format_set *render_formats = + wlr_renderer_get_render_formats(renderer); + assert(render_formats != NULL); + + // Not all clients support fancy formats. Always ensure we provide + // support for ARGB8888 and XRGB8888 for simple clients. + uint32_t fallback_formats[] = { DRM_FORMAT_ARGB8888, DRM_FORMAT_XRGB8888 }; + for (size_t i = 0; i < sizeof(fallback_formats) / sizeof(fallback_formats[0]); i++) { + const struct wlr_drm_format *fmt = + wlr_drm_format_set_get(render_formats, fallback_formats[i]); + if (fmt != NULL && swapchain->format.format != fmt->format) { + add_drm_format(&source->dmabuf_formats, fmt); + } } } From 07e92fb86816783acb5a08d628b961398216ab8e Mon Sep 17 00:00:00 2001 From: Jesper Jensen Date: Tue, 5 Aug 2025 16:16:50 +0200 Subject: [PATCH 093/311] output/cursor: Fix double cursor bug When we fail to render the cursor (in my case because the cursor is too large) we bail out of the output_cursor_attempt_hardware function. This causes output_cursor_set_texture to clean up after us, but we've already cleared the hardware_cursor, and so output_disable_hardware_cursor thinks we don't have a hardware cursor to disable. We shouldn't modify the hardware_cursor variable before we've successfully changed the hardware cursor, this way the caller can clean up after us like it expect to. This was brought up by an actual bug when playing the game Kaizen. Which uses oddly sized cursors, that fell back to software cursors for me, and left the hardware cursor hanging around. This change has been tested to fix that. During the testing of this change, I have noticed that the previous code worked fine the first time the cursor was switch to software. It only failed on subsequent attempts. I haven't figured out why that is. --- types/output/cursor.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/types/output/cursor.c b/types/output/cursor.c index 154b91120..b3ec152ce 100644 --- a/types/output/cursor.c +++ b/types/output/cursor.c @@ -298,8 +298,6 @@ static bool output_cursor_attempt_hardware(struct wlr_output_cursor *cursor) { return false; } - output->hardware_cursor = NULL; - struct wlr_texture *texture = cursor->texture; // If the cursor was hidden or was a software cursor, the hardware From 7392b3313a7b483c61f4fea648ba8f2aa4ce8798 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Tue, 12 Aug 2025 19:00:11 +0200 Subject: [PATCH 094/311] backend, output: send commit events after applying all in wlr_backend_commit() We were iterating over involved outputs, applying the new state and sending the commit event for each one. This resulted in commit events being fired while we weren't done applying the new state for all outputs. Fix this by first applying all of the states, then firing all of the events. Closes: https://github.com/swaywm/sway/issues/8829 --- backend/backend.c | 5 +++++ include/types/wlr_output.h | 1 + types/output/output.c | 3 +++ 3 files changed, 9 insertions(+) diff --git a/backend/backend.c b/backend/backend.c index a130d9045..3d84aa636 100644 --- a/backend/backend.c +++ b/backend/backend.c @@ -485,5 +485,10 @@ bool wlr_backend_commit(struct wlr_backend *backend, output_apply_commit(state->output, &state->base); } + for (size_t i = 0; i < states_len; i++) { + const struct wlr_backend_output_state *state = &states[i]; + output_send_commit_event(state->output, &state->base); + } + return true; } diff --git a/include/types/wlr_output.h b/include/types/wlr_output.h index f901505af..d59b05f0b 100644 --- a/include/types/wlr_output.h +++ b/include/types/wlr_output.h @@ -27,6 +27,7 @@ void output_defer_present(struct wlr_output *output, struct wlr_output_event_pre bool output_prepare_commit(struct wlr_output *output, const struct wlr_output_state *state); void output_apply_commit(struct wlr_output *output, const struct wlr_output_state *state); +void output_send_commit_event(struct wlr_output *output, const struct wlr_output_state *state); void output_state_get_buffer_src_box(const struct wlr_output_state *state, struct wlr_fbox *out); diff --git a/types/output/output.c b/types/output/output.c index 636d155d2..1fb0347a0 100644 --- a/types/output/output.c +++ b/types/output/output.c @@ -759,7 +759,9 @@ void output_apply_commit(struct wlr_output *output, const struct wlr_output_stat } output_apply_state(output, state); +} +void output_send_commit_event(struct wlr_output *output, const struct wlr_output_state *state) { struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); struct wlr_output_event_commit event = { @@ -801,6 +803,7 @@ bool wlr_output_commit_state(struct wlr_output *output, } output_apply_commit(output, &pending); + output_send_commit_event(output, &pending); if (new_back_buffer) { wlr_buffer_unlock(pending.buffer); From 812675ba34ce612e9294e8a9814b1baf4b4775d4 Mon Sep 17 00:00:00 2001 From: Kirill Primak Date: Sun, 12 May 2024 11:53:09 +0300 Subject: [PATCH 095/311] fixes: add implementation --- include/wlr/types/wlr_fixes.h | 28 +++++++++++++++ meson.build | 2 +- types/meson.build | 11 +++--- types/wlr_fixes.c | 65 +++++++++++++++++++++++++++++++++++ 4 files changed, 100 insertions(+), 6 deletions(-) create mode 100644 include/wlr/types/wlr_fixes.h create mode 100644 types/wlr_fixes.c diff --git a/include/wlr/types/wlr_fixes.h b/include/wlr/types/wlr_fixes.h new file mode 100644 index 000000000..b227f7e28 --- /dev/null +++ b/include/wlr/types/wlr_fixes.h @@ -0,0 +1,28 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_FIXES_H +#define WLR_TYPES_WLR_FIXES_H + +#include + +struct wlr_fixes { + struct wl_global *global; + + struct { + struct wl_signal destroy; + } events; + + struct { + struct wl_listener display_destroy; + } WLR_PRIVATE; +}; + +struct wlr_fixes *wlr_fixes_create(struct wl_display *display, uint32_t version); + +#endif diff --git a/meson.build b/meson.build index 8319eff69..4b24e5046 100644 --- a/meson.build +++ b/meson.build @@ -86,7 +86,7 @@ internal_features = { internal_config = configuration_data() wayland_kwargs = { - 'version': '>=1.23.1', + 'version': '>=1.24.0', 'fallback': 'wayland', 'default_options': [ 'tests=false', diff --git a/types/meson.build b/types/meson.build index 25a0d4434..402fd3e11 100644 --- a/types/meson.build +++ b/types/meson.build @@ -39,19 +39,20 @@ wlr_files += files( 'buffer/resource.c', 'wlr_alpha_modifier_v1.c', 'wlr_color_management_v1.c', + 'wlr_color_representation_v1.c', 'wlr_compositor.c', 'wlr_content_type_v1.c', - 'wlr_cursor_shape_v1.c', 'wlr_cursor.c', + 'wlr_cursor_shape_v1.c', 'wlr_damage_ring.c', 'wlr_data_control_v1.c', 'wlr_drm.c', 'wlr_export_dmabuf_v1.c', - 'wlr_foreign_toplevel_management_v1.c', - 'wlr_color_representation_v1.c', - 'wlr_ext_image_copy_capture_v1.c', - 'wlr_ext_foreign_toplevel_list_v1.c', 'wlr_ext_data_control_v1.c', + 'wlr_ext_foreign_toplevel_list_v1.c', + 'wlr_ext_image_copy_capture_v1.c', + 'wlr_fixes.c', + 'wlr_foreign_toplevel_management_v1.c', 'wlr_fractional_scale_v1.c', 'wlr_gamma_control_v1.c', 'wlr_idle_inhibit_v1.c', diff --git a/types/wlr_fixes.c b/types/wlr_fixes.c new file mode 100644 index 000000000..b5435aaf8 --- /dev/null +++ b/types/wlr_fixes.c @@ -0,0 +1,65 @@ +#include +#include +#include + +#include + +#define FIXES_VERSION 1 + +static void fixes_destroy(struct wl_client *client, struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static void fixes_destroy_registry(struct wl_client *client, struct wl_resource *resource, + struct wl_resource *registry) { + wl_resource_destroy(registry); +} + +static const struct wl_fixes_interface fixes_impl = { + .destroy = fixes_destroy, + .destroy_registry = fixes_destroy_registry, +}; + +static void fixes_bind(struct wl_client *wl_client, void *data, uint32_t version, uint32_t id) { + struct wlr_fixes *fixes = data; + + struct wl_resource *resource = wl_resource_create(wl_client, &wl_fixes_interface, version, id); + if (resource == NULL) { + wl_client_post_no_memory(wl_client); + return; + } + wl_resource_set_implementation(resource, &fixes_impl, fixes, NULL); +} + +static void fixes_handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_fixes *fixes = wl_container_of(listener, fixes, display_destroy); + wl_signal_emit_mutable(&fixes->events.destroy, NULL); + + assert(wl_list_empty(&fixes->events.destroy.listener_list)); + + wl_list_remove(&fixes->display_destroy.link); + wl_global_destroy(fixes->global); + free(fixes); +} + +struct wlr_fixes *wlr_fixes_create(struct wl_display *display, uint32_t version) { + assert(version <= FIXES_VERSION); + + struct wlr_fixes *fixes = calloc(1, sizeof(*fixes)); + if (fixes == NULL) { + return NULL; + } + + fixes->global = wl_global_create(display, &wl_fixes_interface, version, fixes, fixes_bind); + if (fixes->global == NULL) { + free(fixes); + return NULL; + } + + wl_signal_init(&fixes->events.destroy); + + fixes->display_destroy.notify = fixes_handle_display_destroy; + wl_display_add_destroy_listener(display, &fixes->display_destroy); + + return fixes; +} From ad1b2f28199f41cfa5218eb1456500716801894f Mon Sep 17 00:00:00 2001 From: Kirill Primak Date: Tue, 20 May 2025 21:52:17 +0300 Subject: [PATCH 096/311] Avoid including generated headers publicly where possible This is possible now that w-p ships enum headers. The remaining includes are from wlr-protocols. --- include/wlr/types/wlr_color_management_v1.h | 6 +++--- include/wlr/types/wlr_color_representation_v1.h | 3 +-- include/wlr/types/wlr_content_type_v1.h | 2 +- include/wlr/types/wlr_cursor_shape_v1.h | 2 +- include/wlr/types/wlr_ext_image_copy_capture_v1.h | 4 ++-- include/wlr/types/wlr_pointer_constraints_v1.h | 2 +- include/wlr/types/wlr_tablet_v2.h | 3 +-- include/wlr/types/wlr_tearing_control_v1.h | 4 +--- include/wlr/types/wlr_xdg_shell.h | 2 +- types/wlr_color_management_v1.c | 1 + types/wlr_content_type_v1.c | 2 ++ types/wlr_cursor_shape_v1.c | 2 ++ types/wlr_ext_image_copy_capture_v1.c | 1 + types/wlr_pointer_constraints_v1.c | 2 ++ 14 files changed, 20 insertions(+), 16 deletions(-) diff --git a/include/wlr/types/wlr_color_management_v1.h b/include/wlr/types/wlr_color_management_v1.h index f369fb22c..4a50e94e3 100644 --- a/include/wlr/types/wlr_color_management_v1.h +++ b/include/wlr/types/wlr_color_management_v1.h @@ -9,10 +9,10 @@ #ifndef WLR_TYPES_WLR_COLOR_MANAGEMENT_V1_H #define WLR_TYPES_WLR_COLOR_MANAGEMENT_V1_H -#include -#include +#include +#include -#include "color-management-v1-protocol.h" +#include struct wlr_surface; diff --git a/include/wlr/types/wlr_color_representation_v1.h b/include/wlr/types/wlr_color_representation_v1.h index d575eedf5..0a1958dec 100644 --- a/include/wlr/types/wlr_color_representation_v1.h +++ b/include/wlr/types/wlr_color_representation_v1.h @@ -10,10 +10,9 @@ #define WLR_TYPES_WLR_COLOR_REPRESENTATION_V1_H #include +#include #include -#include "color-representation-v1-protocol.h" - struct wlr_surface; // Supported coefficients and range are always paired together diff --git a/include/wlr/types/wlr_content_type_v1.h b/include/wlr/types/wlr_content_type_v1.h index 31c51ac4c..1bea899eb 100644 --- a/include/wlr/types/wlr_content_type_v1.h +++ b/include/wlr/types/wlr_content_type_v1.h @@ -10,7 +10,7 @@ #define WLR_TYPES_WLR_CONTENT_TYPE_V1_H #include -#include "content-type-v1-protocol.h" +#include struct wlr_surface; diff --git a/include/wlr/types/wlr_cursor_shape_v1.h b/include/wlr/types/wlr_cursor_shape_v1.h index 26048a52e..d0a21aeb9 100644 --- a/include/wlr/types/wlr_cursor_shape_v1.h +++ b/include/wlr/types/wlr_cursor_shape_v1.h @@ -10,7 +10,7 @@ #define WLR_TYPES_WLR_CURSOR_SHAPE_V1_H #include -#include "cursor-shape-v1-protocol.h" +#include /** * Manager for the cursor-shape-v1 protocol. diff --git a/include/wlr/types/wlr_ext_image_copy_capture_v1.h b/include/wlr/types/wlr_ext_image_copy_capture_v1.h index 51c4af8c4..9b1ab3df1 100644 --- a/include/wlr/types/wlr_ext_image_copy_capture_v1.h +++ b/include/wlr/types/wlr_ext_image_copy_capture_v1.h @@ -10,9 +10,9 @@ #define WLR_TYPES_WLR_EXT_IMAGE_COPY_CAPTURE_V1_H #include -#include +#include +#include #include -#include "ext-image-copy-capture-v1-protocol.h" struct wlr_renderer; diff --git a/include/wlr/types/wlr_pointer_constraints_v1.h b/include/wlr/types/wlr_pointer_constraints_v1.h index bccb23090..0d74ce56d 100644 --- a/include/wlr/types/wlr_pointer_constraints_v1.h +++ b/include/wlr/types/wlr_pointer_constraints_v1.h @@ -11,10 +11,10 @@ #include #include +#include #include #include #include -#include "pointer-constraints-unstable-v1-protocol.h" struct wlr_seat; diff --git a/include/wlr/types/wlr_tablet_v2.h b/include/wlr/types/wlr_tablet_v2.h index dc911f101..599e88019 100644 --- a/include/wlr/types/wlr_tablet_v2.h +++ b/include/wlr/types/wlr_tablet_v2.h @@ -10,10 +10,9 @@ #define WLR_TYPES_WLR_TABLET_V2_H #include +#include #include -#include "tablet-v2-protocol.h" - /* This can probably be even lower,the tools don't have a lot of buttons */ #define WLR_TABLET_V2_TOOL_BUTTONS_CAP 16 diff --git a/include/wlr/types/wlr_tearing_control_v1.h b/include/wlr/types/wlr_tearing_control_v1.h index ff981e414..f2d4a1478 100644 --- a/include/wlr/types/wlr_tearing_control_v1.h +++ b/include/wlr/types/wlr_tearing_control_v1.h @@ -11,11 +11,9 @@ #include #include -#include +#include #include -#include "tearing-control-v1-protocol.h" - struct wlr_tearing_control_v1 { struct wl_client *client; struct wl_list link; diff --git a/include/wlr/types/wlr_xdg_shell.h b/include/wlr/types/wlr_xdg_shell.h index a835bb107..517d77442 100644 --- a/include/wlr/types/wlr_xdg_shell.h +++ b/include/wlr/types/wlr_xdg_shell.h @@ -10,10 +10,10 @@ #define WLR_TYPES_WLR_XDG_SHELL_H #include +#include #include #include #include -#include "xdg-shell-protocol.h" struct wlr_xdg_shell { struct wl_global *global; diff --git a/types/wlr_color_management_v1.c b/types/wlr_color_management_v1.c index 3af0b51e8..75da825cb 100644 --- a/types/wlr_color_management_v1.c +++ b/types/wlr_color_management_v1.c @@ -7,6 +7,7 @@ #include #include +#include "color-management-v1-protocol.h" #include "render/color.h" #include "util/mem.h" diff --git a/types/wlr_content_type_v1.c b/types/wlr_content_type_v1.c index 0c59859df..d3302aef1 100644 --- a/types/wlr_content_type_v1.c +++ b/types/wlr_content_type_v1.c @@ -3,6 +3,8 @@ #include #include +#include "content-type-v1-protocol.h" + #define CONTENT_TYPE_VERSION 1 struct wlr_content_type_v1_surface { diff --git a/types/wlr_cursor_shape_v1.c b/types/wlr_cursor_shape_v1.c index b8ae07136..563b326e0 100644 --- a/types/wlr_cursor_shape_v1.c +++ b/types/wlr_cursor_shape_v1.c @@ -5,6 +5,8 @@ #include #include #include + +#include "cursor-shape-v1-protocol.h" #include "types/wlr_tablet_v2.h" #define CURSOR_SHAPE_MANAGER_V1_VERSION 2 diff --git a/types/wlr_ext_image_copy_capture_v1.c b/types/wlr_ext_image_copy_capture_v1.c index 6f5556139..c4cfd743e 100644 --- a/types/wlr_ext_image_copy_capture_v1.c +++ b/types/wlr_ext_image_copy_capture_v1.c @@ -6,6 +6,7 @@ #include #include #include +#include "ext-image-copy-capture-v1-protocol.h" #include "render/pixel_format.h" #define IMAGE_COPY_CAPTURE_MANAGER_V1_VERSION 1 diff --git a/types/wlr_pointer_constraints_v1.c b/types/wlr_pointer_constraints_v1.c index 6bc5efc1b..51a2304d8 100644 --- a/types/wlr_pointer_constraints_v1.c +++ b/types/wlr_pointer_constraints_v1.c @@ -9,6 +9,8 @@ #include #include +#include "pointer-constraints-unstable-v1-protocol.h" + static const struct zwp_locked_pointer_v1_interface locked_pointer_impl; static const struct zwp_confined_pointer_v1_interface confined_pointer_impl; static const struct zwp_pointer_constraints_v1_interface pointer_constraints_impl; From bb1f8673b3a4883b8c0f3185a6ec3bbe410d9307 Mon Sep 17 00:00:00 2001 From: Kirill Primak Date: Fri, 9 Aug 2024 23:33:14 +0300 Subject: [PATCH 097/311] compositor: use wl_resource_post_error_vargs() --- types/wlr_compositor.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/types/wlr_compositor.c b/types/wlr_compositor.c index d2c881e2a..6b31ab857 100644 --- a/types/wlr_compositor.c +++ b/types/wlr_compositor.c @@ -882,11 +882,7 @@ void wlr_surface_reject_pending(struct wlr_surface *surface, struct wl_resource va_list args; va_start(args, msg); - // XXX: libwayland could expose wl_resource_post_error_vargs() instead - char buffer[128]; // Matches the size of the buffer used in libwayland - vsnprintf(buffer, sizeof(buffer), msg, args); - - wl_resource_post_error(resource, code, "%s", buffer); + wl_resource_post_error_vargs(resource, code, msg, args); surface->pending_rejected = true; va_end(args); From 7431d840d0c29dd90b21c87698b39769e4f2f004 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Mon, 18 Aug 2025 14:09:13 +0200 Subject: [PATCH 098/311] color-management-v1: handle inert outputs in get_output wlr_output_from_resource() can return NULL if the outputs no longer exists on the compositor side. Closes: https://github.com/swaywm/sway/issues/8847 --- types/wlr_color_management_v1.c | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/types/wlr_color_management_v1.c b/types/wlr_color_management_v1.c index 75da825cb..55faaacaa 100644 --- a/types/wlr_color_management_v1.c +++ b/types/wlr_color_management_v1.c @@ -714,30 +714,35 @@ static void manager_handle_get_output(struct wl_client *client, struct wlr_color_manager_v1 *manager = manager_from_resource(manager_resource); struct wlr_output *output = wlr_output_from_resource(output_resource); + uint32_t version = wl_resource_get_version(manager_resource); + struct wl_resource *cm_output_resource = wl_resource_create(client, + &wp_color_management_output_v1_interface, version, id); + if (!cm_output_resource) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(cm_output_resource, &cm_output_impl, + NULL, cm_output_handle_resource_destroy); + + if (output == NULL) { + return; // leave the wp_color_management_output_v1 resource inert + } + struct wlr_color_management_output_v1 *cm_output = calloc(1, sizeof(*cm_output)); if (cm_output == NULL) { wl_client_post_no_memory(client); return; } + cm_output->resource = cm_output_resource; cm_output->manager = manager; cm_output->output = output; - uint32_t version = wl_resource_get_version(manager_resource); - cm_output->resource = wl_resource_create(client, - &wp_color_management_output_v1_interface, version, id); - if (!cm_output->resource) { - wl_client_post_no_memory(client); - free(cm_output); - return; - } - wl_resource_set_implementation(cm_output->resource, &cm_output_impl, - cm_output, cm_output_handle_resource_destroy); - cm_output->output_destroy.notify = cm_output_handle_output_destroy; wl_signal_add(&output->events.destroy, &cm_output->output_destroy); wl_list_insert(&manager->outputs, &cm_output->link); + wl_resource_set_user_data(cm_output->resource, cm_output); } static struct wlr_color_management_surface_v1 *cm_surface_from_surface(struct wlr_surface *surface) { From b0c886ec77dba9486beae90f4007c38987f3d4f8 Mon Sep 17 00:00:00 2001 From: xurui Date: Mon, 25 Aug 2025 13:58:03 +0800 Subject: [PATCH 099/311] render/allocator/gbm: insert buffer after export gbm bo Signed-off-by: xurui --- render/allocator/gbm.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/render/allocator/gbm.c b/render/allocator/gbm.c index ca16177d1..fadfac9f8 100644 --- a/render/allocator/gbm.c +++ b/render/allocator/gbm.c @@ -97,7 +97,6 @@ static struct wlr_gbm_buffer *create_buffer(struct wlr_gbm_allocator *alloc, } wlr_buffer_init(&buffer->base, &buffer_impl, width, height); buffer->gbm_bo = bo; - wl_list_insert(&alloc->buffers, &buffer->link); if (!export_gbm_bo(bo, &buffer->dmabuf)) { free(buffer); @@ -112,6 +111,8 @@ static struct wlr_gbm_buffer *create_buffer(struct wlr_gbm_allocator *alloc, buffer->dmabuf.modifier = fallback_modifier; } + wl_list_insert(&alloc->buffers, &buffer->link); + char *format_name = drmGetFormatName(buffer->dmabuf.format); char *modifier_name = drmGetFormatModifierName(buffer->dmabuf.modifier); wlr_log(WLR_DEBUG, "Allocated %dx%d GBM buffer " From 7bf5ff4c0286e43fc0a40b79ce8fe3f80e52ace7 Mon Sep 17 00:00:00 2001 From: xurui Date: Wed, 27 Aug 2025 20:01:50 +0800 Subject: [PATCH 100/311] wlr_xdg_toplevel_icon_v1: check the correct resource Signed-off-by: xurui --- types/wlr_xdg_toplevel_icon_v1.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/wlr_xdg_toplevel_icon_v1.c b/types/wlr_xdg_toplevel_icon_v1.c index 5927b8168..e67ad5378 100644 --- a/types/wlr_xdg_toplevel_icon_v1.c +++ b/types/wlr_xdg_toplevel_icon_v1.c @@ -153,7 +153,7 @@ static void manager_handle_create_icon(struct wl_client *client, struct wl_resou struct wl_resource *icon_resource = wl_resource_create(client, &xdg_toplevel_icon_v1_interface, wl_resource_get_version(resource), id); - if (resource == NULL) { + if (icon_resource == NULL) { wl_client_post_no_memory(client); free(icon); return; From bbd9a49bdf1449886b8e8e12a96455a1e2228143 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sat, 16 Aug 2025 19:21:17 +0200 Subject: [PATCH 101/311] tinywl: stop generating xdg-shell header We don't need to do this anymore for wayland-protocols. --- tinywl/Makefile | 13 ++----------- tinywl/meson.build | 2 +- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/tinywl/Makefile b/tinywl/Makefile index 70dc671ca..9c7af540e 100644 --- a/tinywl/Makefile +++ b/tinywl/Makefile @@ -1,6 +1,4 @@ PKG_CONFIG?=pkg-config -WAYLAND_PROTOCOLS!=$(PKG_CONFIG) --variable=pkgdatadir wayland-protocols -WAYLAND_SCANNER!=$(PKG_CONFIG) --variable=wayland_scanner wayland-scanner PKGS="wlroots-0.20" wayland-server xkbcommon CFLAGS_PKG_CONFIG!=$(PKG_CONFIG) --cflags $(PKGS) @@ -9,19 +7,12 @@ LIBS!=$(PKG_CONFIG) --libs $(PKGS) all: tinywl -# wayland-scanner is a tool which generates C headers and rigging for Wayland -# protocols, which are specified in XML. wlroots requires you to rig these up -# to your build system yourself and provide them in the include path. -xdg-shell-protocol.h: - $(WAYLAND_SCANNER) server-header \ - $(WAYLAND_PROTOCOLS)/stable/xdg-shell/xdg-shell.xml $@ - -tinywl.o: tinywl.c xdg-shell-protocol.h +tinywl.o: tinywl.c $(CC) -c $< -g -Werror $(CFLAGS) -I. -DWLR_USE_UNSTABLE -o $@ tinywl: tinywl.o $(CC) $^ $> -g -Werror $(CFLAGS) $(LDFLAGS) $(LIBS) -o $@ clean: - rm -f tinywl tinywl.o xdg-shell-protocol.h + rm -f tinywl tinywl.o .PHONY: all clean diff --git a/tinywl/meson.build b/tinywl/meson.build index e7271458b..07b4a5e99 100644 --- a/tinywl/meson.build +++ b/tinywl/meson.build @@ -1,5 +1,5 @@ executable( 'tinywl', - ['tinywl.c', protocols_server_header['xdg-shell']], + 'tinywl.c', dependencies: wlroots, ) From 1a18e47efa7878d985c5522e1df2100f0430ddb1 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sat, 16 Aug 2025 11:44:24 +0200 Subject: [PATCH 102/311] render/vulkan: fix VkPushConstantRange for wlr_vk_frag_texture_pcr_data We pass an alpha multiplier plus a luminance multiplier now. Fixes the following validation layer error: vkCmdPushConstants(): is called with stageFlags (VK_SHADER_STAGE_FRAGMENT_BIT), offset (80), size (72) but the VkPipelineLayout 0x510000000051 doesn't have a VkPushConstantRange with VK_SHADER_STAGE_FRAGMENT_BIT. The Vulkan spec states: For each byte in the range specified by offset and size and for each shader stage in stageFlags, there must be a push constant range in layout that includes that byte and that stage (https://docs.vulkan.org/spec/latest/chapters/descriptorsets.html#VUID-vkCmdPushConstants-offset-01795) (VUID-vkCmdPushConstants-offset-01795) Fixes: 56d95c2ecb2f ("render/vulkan: introduce wlr_vk_frag_texture_pcr_data") --- render/vulkan/renderer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/render/vulkan/renderer.c b/render/vulkan/renderer.c index 266ac61cf..6a166a546 100644 --- a/render/vulkan/renderer.c +++ b/render/vulkan/renderer.c @@ -1473,7 +1473,7 @@ static bool init_tex_layouts(struct wlr_vk_renderer *renderer, }, { .offset = pc_ranges[0].size, - .size = sizeof(float) * 4, // alpha or color + .size = sizeof(struct wlr_vk_frag_texture_pcr_data), .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT, }, }; From e95117b700f2079493a2cd2eb4b8883b431adc94 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sat, 16 Aug 2025 11:53:12 +0200 Subject: [PATCH 103/311] render/vulkan: remove hardcoded counts Use the array size instead. --- render/vulkan/renderer.c | 50 ++++++++++++++++++++-------------------- render/vulkan/texture.c | 8 +++---- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/render/vulkan/renderer.c b/render/vulkan/renderer.c index 6a166a546..08d99bb7c 100644 --- a/render/vulkan/renderer.c +++ b/render/vulkan/renderer.c @@ -771,13 +771,13 @@ bool vulkan_setup_plain_framebuffer(struct wlr_vk_render_buffer *buffer, }; vkUpdateDescriptorSets(dev, 1, &ds_write, 0, NULL); - VkImageView attachments[2] = { + VkImageView attachments[] = { buffer->plain.blend_image_view, buffer->plain.image_view }; VkFramebufferCreateInfo fb_info = { .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, - .attachmentCount = 2, + .attachmentCount = sizeof(attachments) / sizeof(attachments[0]), .pAttachments = attachments, .flags = 0u, .width = dmabuf->width, @@ -1466,7 +1466,7 @@ static bool init_tex_layouts(struct wlr_vk_renderer *renderer, return false; } - VkPushConstantRange pc_ranges[2] = { + VkPushConstantRange pc_ranges[] = { { .size = sizeof(struct wlr_vk_vert_pcr_data), .stageFlags = VK_SHADER_STAGE_VERTEX_BIT, @@ -1482,7 +1482,7 @@ static bool init_tex_layouts(struct wlr_vk_renderer *renderer, .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, .setLayoutCount = 1, .pSetLayouts = out_ds_layout, - .pushConstantRangeCount = 2, + .pushConstantRangeCount = sizeof(pc_ranges) / sizeof(pc_ranges[0]), .pPushConstantRanges = pc_ranges, }; @@ -1560,7 +1560,7 @@ static bool init_blend_to_output_layouts(struct wlr_vk_renderer *renderer) { } // pipeline layout -- standard vertex uniforms, no shader uniforms - VkPushConstantRange pc_ranges[2] = { + VkPushConstantRange pc_ranges[] = { { .offset = 0, .size = sizeof(struct wlr_vk_vert_pcr_data), @@ -1573,16 +1573,16 @@ static bool init_blend_to_output_layouts(struct wlr_vk_renderer *renderer) { }, }; - VkDescriptorSetLayout out_ds_layouts[2] = { + VkDescriptorSetLayout out_ds_layouts[] = { renderer->output_ds_srgb_layout, renderer->output_ds_lut3d_layout, }; VkPipelineLayoutCreateInfo pl_info = { .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, - .setLayoutCount = 2, + .setLayoutCount = sizeof(out_ds_layouts) / sizeof(out_ds_layouts[0]), .pSetLayouts = out_ds_layouts, - .pushConstantRangeCount = 2, + .pushConstantRangeCount = sizeof(pc_ranges) / sizeof(pc_ranges[0]), .pPushConstantRanges = pc_ranges, }; @@ -1755,14 +1755,14 @@ struct wlr_vk_pipeline *setup_get_or_create_pipeline( .scissorCount = 1, }; - VkDynamicState dynStates[2] = { + VkDynamicState dyn_states[] = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR, }; VkPipelineDynamicStateCreateInfo dynamic = { .sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO, - .pDynamicStates = dynStates, - .dynamicStateCount = 2, + .pDynamicStates = dyn_states, + .dynamicStateCount = sizeof(dyn_states) / sizeof(dyn_states[0]), }; VkPipelineVertexInputStateCreateInfo vertex = { @@ -1774,7 +1774,7 @@ struct wlr_vk_pipeline *setup_get_or_create_pipeline( .layout = pipeline_layout->vk, .renderPass = setup->render_pass, .subpass = 0, - .stageCount = 2, + .stageCount = sizeof(stages) / sizeof(stages[0]), .pStages = stages, .pInputAssemblyState = &assembly, @@ -1817,7 +1817,7 @@ static bool init_blend_to_output_pipeline(struct wlr_vk_renderer *renderer, .pData = &output_transform_type, }; - VkPipelineShaderStageCreateInfo tex_stages[2] = { + VkPipelineShaderStageCreateInfo tex_stages[] = { { .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage = VK_SHADER_STAGE_VERTEX_BIT, @@ -1872,14 +1872,14 @@ static bool init_blend_to_output_pipeline(struct wlr_vk_renderer *renderer, .scissorCount = 1, }; - VkDynamicState dynStates[2] = { + VkDynamicState dyn_states[] = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR, }; VkPipelineDynamicStateCreateInfo dynamic = { .sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO, - .pDynamicStates = dynStates, - .dynamicStateCount = 2, + .pDynamicStates = dyn_states, + .dynamicStateCount = sizeof(dyn_states) / sizeof(dyn_states[0]), }; VkPipelineVertexInputStateCreateInfo vertex = { @@ -1892,7 +1892,7 @@ static bool init_blend_to_output_pipeline(struct wlr_vk_renderer *renderer, .layout = pipe_layout, .renderPass = rp, .subpass = 1, // second subpass! - .stageCount = 2, + .stageCount = sizeof(tex_stages) / sizeof(tex_stages[0]), .pStages = tex_stages, .pInputAssemblyState = &assembly, .pRasterizationState = &rasterization, @@ -2185,7 +2185,7 @@ static struct wlr_vk_render_format_setup *find_or_create_render_setup( VkResult res; if (use_blending_buffer) { - VkAttachmentDescription attachments[2] = { + VkAttachmentDescription attachments[] = { { .format = VK_FORMAT_R16G16B16A16_SFLOAT, .samples = VK_SAMPLE_COUNT_1_BIT, @@ -2223,7 +2223,7 @@ static struct wlr_vk_render_format_setup *find_or_create_render_setup( .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, }; - VkSubpassDescription subpasses[2] = { + VkSubpassDescription subpasses[] = { { .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, .colorAttachmentCount = 1, @@ -2238,7 +2238,7 @@ static struct wlr_vk_render_format_setup *find_or_create_render_setup( } }; - VkSubpassDependency deps[3] = { + VkSubpassDependency deps[] = { { .srcSubpass = VK_SUBPASS_EXTERNAL, .srcStageMask = VK_PIPELINE_STAGE_HOST_BIT | @@ -2280,11 +2280,11 @@ static struct wlr_vk_render_format_setup *find_or_create_render_setup( .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, .pNext = NULL, .flags = 0, - .attachmentCount = 2u, + .attachmentCount = sizeof(attachments) / sizeof(attachments[0]), .pAttachments = attachments, - .subpassCount = 2u, + .subpassCount = sizeof(subpasses) / sizeof(subpasses[0]), .pSubpasses = subpasses, - .dependencyCount = 3u, + .dependencyCount = sizeof(deps) / sizeof(deps[0]), .pDependencies = deps, }; @@ -2339,7 +2339,7 @@ static struct wlr_vk_render_format_setup *find_or_create_render_setup( .pColorAttachments = &color_ref, }; - VkSubpassDependency deps[2] = { + VkSubpassDependency deps[] = { { .srcSubpass = VK_SUBPASS_EXTERNAL, .srcStageMask = VK_PIPELINE_STAGE_HOST_BIT | @@ -2374,7 +2374,7 @@ static struct wlr_vk_render_format_setup *find_or_create_render_setup( .pAttachments = &attachment, .subpassCount = 1, .pSubpasses = &subpass, - .dependencyCount = 2u, + .dependencyCount = sizeof(deps) / sizeof(deps[0]), .pDependencies = deps, }; diff --git a/render/vulkan/texture.c b/render/vulkan/texture.c index 2b21d458c..499178f5d 100644 --- a/render/vulkan/texture.c +++ b/render/vulkan/texture.c @@ -399,14 +399,14 @@ static struct wlr_texture *vulkan_texture_from_pixels( texture_set_format(texture, &fmt->format, fmt->shm.has_mutable_srgb); - VkFormat view_formats[2] = { + VkFormat view_formats[] = { fmt->format.vk, fmt->format.vk_srgb, }; VkImageFormatListCreateInfoKHR list_info = { .sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO_KHR, .pViewFormats = view_formats, - .viewFormatCount = 2, + .viewFormatCount = sizeof(view_formats) / sizeof(view_formats[0]), }; VkImageCreateInfo img_info = { .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, @@ -600,14 +600,14 @@ VkImage vulkan_import_dmabuf(struct wlr_vk_renderer *renderer, }; eimg.pNext = &mod_info; - VkFormat view_formats[2] = { + VkFormat view_formats[] = { fmt->format.vk, fmt->format.vk_srgb, }; VkImageFormatListCreateInfoKHR list_info = { .sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO_KHR, .pViewFormats = view_formats, - .viewFormatCount = 2, + .viewFormatCount = sizeof(view_formats) / sizeof(view_formats[0]), }; if (mod->has_mutable_srgb) { mod_info.pNext = &list_info; From b799ffc6aee6466b4e735b34bcd1d5cb8be476b1 Mon Sep 17 00:00:00 2001 From: rewine Date: Wed, 30 Jul 2025 18:25:22 +0800 Subject: [PATCH 104/311] docs: deprecate legacy wlr_data_control_v1 interface Add deprecation notice for wlr_data_control_v1, indicating that it's superseded by ext-data-control-v1. Related: https://gitlab.freedesktop.org/wlroots/wlr-protocols/-/merge_requests/136 --- include/wlr/types/wlr_data_control_v1.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/include/wlr/types/wlr_data_control_v1.h b/include/wlr/types/wlr_data_control_v1.h index 9ef86d7cf..4dd642bfe 100644 --- a/include/wlr/types/wlr_data_control_v1.h +++ b/include/wlr/types/wlr_data_control_v1.h @@ -12,6 +12,12 @@ #include #include +/** + * Deprecated: this protocol is legacy and superseded by ext-data-control-v1. + * The implementation will be dropped in a future wlroots version. + * + * Consider using `wlr_ext_data_control_manager_v1` as a replacement. + */ struct wlr_data_control_manager_v1 { struct wl_global *global; struct wl_list devices; // wlr_data_control_device_v1.link From 122310a2de35a24b5c886112e8cca7db1cf84ade Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sat, 16 Aug 2025 19:19:52 +0200 Subject: [PATCH 105/311] build: add wayland-protocols to dependencies array We grab header files from there, ensure include directories are properly set up when building wlroots. Fixes missing header files when a wayland-protocols subproject is used. --- protocol/meson.build | 1 + 1 file changed, 1 insertion(+) diff --git a/protocol/meson.build b/protocol/meson.build index 5012753b5..613d18018 100644 --- a/protocol/meson.build +++ b/protocol/meson.build @@ -4,6 +4,7 @@ wayland_protos = dependency('wayland-protocols', default_options: ['tests=false'], ) wl_protocol_dir = wayland_protos.get_variable('pkgdatadir') +wlr_deps += wayland_protos wayland_scanner_dep = dependency('wayland-scanner', kwargs: wayland_kwargs, From 423afc3fc97e03f7ec523cb4fa6621119378ae4b Mon Sep 17 00:00:00 2001 From: Simon Zeni Date: Wed, 27 Aug 2025 15:01:43 -0400 Subject: [PATCH 106/311] types: deprecate wlr-screencopy-unstable-v1 --- include/wlr/types/wlr_screencopy_v1.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/include/wlr/types/wlr_screencopy_v1.h b/include/wlr/types/wlr_screencopy_v1.h index 3db958ad1..4f53a603c 100644 --- a/include/wlr/types/wlr_screencopy_v1.h +++ b/include/wlr/types/wlr_screencopy_v1.h @@ -14,6 +14,13 @@ #include #include +/** + * Deprecated: this protocol is deprecated and superseded by ext-image-copy-capture-v1. + * The implementation will be dropped in a future wlroots version. + * + * Consider using `wlr_ext_image_capture_source_v1` instead. + */ + struct wlr_screencopy_manager_v1 { struct wl_global *global; struct wl_list frames; // wlr_screencopy_frame_v1.link From 0166fd9eb778761295ea14fdff0515ada1a1cb17 Mon Sep 17 00:00:00 2001 From: Simon Zeni Date: Fri, 4 Jul 2025 15:34:14 -0400 Subject: [PATCH 107/311] drm-lease-v1: remove connector active_lease & lease connectors Upon leasing, the wlr_drm_lease_connector_v1 will be automatically clean up by the wlr_output destroy handler. There is no need for the wlr_drm_lease_manager to keep track of leased connectors. --- include/wlr/types/wlr_drm_lease_v1.h | 5 ---- types/wlr_drm_lease_v1.c | 37 ---------------------------- 2 files changed, 42 deletions(-) diff --git a/include/wlr/types/wlr_drm_lease_v1.h b/include/wlr/types/wlr_drm_lease_v1.h index 752f6efa7..b29a5f6dc 100644 --- a/include/wlr/types/wlr_drm_lease_v1.h +++ b/include/wlr/types/wlr_drm_lease_v1.h @@ -62,8 +62,6 @@ struct wlr_drm_lease_connector_v1 { struct wlr_output *output; struct wlr_drm_lease_device_v1 *device; - /** NULL if no client is currently leasing this connector */ - struct wlr_drm_lease_v1 *active_lease; struct wl_list link; // wlr_drm_lease_device_v1.connectors @@ -93,9 +91,6 @@ struct wlr_drm_lease_v1 { struct wlr_drm_lease_device_v1 *device; - struct wlr_drm_lease_connector_v1 **connectors; - size_t n_connectors; - struct wl_list link; // wlr_drm_lease_device_v1.leases void *data; diff --git a/types/wlr_drm_lease_v1.c b/types/wlr_drm_lease_v1.c index c64bb0896..892a401b3 100644 --- a/types/wlr_drm_lease_v1.c +++ b/types/wlr_drm_lease_v1.c @@ -68,10 +68,6 @@ static void drm_lease_connector_v1_destroy( wlr_log(WLR_DEBUG, "Destroying connector %s", connector->output->name); - if (connector->active_lease) { - wlr_drm_lease_terminate(connector->active_lease->drm_lease); - } - struct wl_resource *resource, *tmp; wl_resource_for_each_safe(resource, tmp, &connector->resources) { wp_drm_lease_connector_v1_send_withdrawn(resource); @@ -140,14 +136,9 @@ static void lease_handle_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&lease->destroy.link); - for (size_t i = 0; i < lease->n_connectors; ++i) { - lease->connectors[i]->active_lease = NULL; - } - wl_list_remove(&lease->link); wl_resource_set_user_data(lease->resource, NULL); - free(lease->connectors); free(lease); } @@ -180,20 +171,6 @@ struct wlr_drm_lease_v1 *wlr_drm_lease_request_v1_grant( return NULL; } - lease->connectors = calloc(request->n_connectors, sizeof(*lease->connectors)); - if (!lease->connectors) { - wlr_log(WLR_ERROR, "Failed to allocate lease connectors list"); - close(fd); - wp_drm_lease_v1_send_finished(lease->resource); - free(lease); - return NULL; - } - lease->n_connectors = request->n_connectors; - for (size_t i = 0; i < request->n_connectors; ++i) { - lease->connectors[i] = request->connectors[i]; - lease->connectors[i]->active_lease = lease; - } - lease->destroy.notify = lease_handle_destroy; wl_signal_add(&lease->drm_lease->events.destroy, &lease->destroy); @@ -338,16 +315,6 @@ static void drm_lease_request_v1_handle_submit( return; } - for (size_t i = 0; i < request->n_connectors; ++i) { - struct wlr_drm_lease_connector_v1 *conn = request->connectors[i]; - if (conn->active_lease) { - wlr_log(WLR_ERROR, "Failed to create lease, connector %s has " - "already been leased", conn->output->name); - wp_drm_lease_v1_send_finished(lease_resource); - return; - } - } - request->lease_resource = lease_resource; wl_signal_emit_mutable(&request->device->manager->events.request, @@ -440,10 +407,6 @@ static struct wp_drm_lease_connector_v1_interface lease_connector_impl = { static void drm_lease_connector_v1_send_to_client( struct wlr_drm_lease_connector_v1 *connector, struct wl_resource *resource) { - if (connector->active_lease) { - return; - } - struct wl_client *client = wl_resource_get_client(resource); uint32_t version = wl_resource_get_version(resource); From 06aacb2a6fd237a5e1062d611909432bbcf5b566 Mon Sep 17 00:00:00 2001 From: tokyo4j Date: Mon, 7 Jul 2025 05:58:48 +0900 Subject: [PATCH 108/311] input-method: rename input_method event to new_input_method --- include/wlr/types/wlr_input_method_v2.h | 2 +- types/wlr_input_method_v2.c | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/include/wlr/types/wlr_input_method_v2.h b/include/wlr/types/wlr_input_method_v2.h index 77460b571..885b3fe80 100644 --- a/include/wlr/types/wlr_input_method_v2.h +++ b/include/wlr/types/wlr_input_method_v2.h @@ -94,7 +94,7 @@ struct wlr_input_method_manager_v2 { struct wl_list input_methods; // struct wlr_input_method_v2.link struct { - struct wl_signal input_method; // struct wlr_input_method_v2 + struct wl_signal new_input_method; // struct wlr_input_method_v2 struct wl_signal destroy; // struct wlr_input_method_manager_v2 } events; diff --git a/types/wlr_input_method_v2.c b/types/wlr_input_method_v2.c index ae50d784c..ed2a4ebeb 100644 --- a/types/wlr_input_method_v2.c +++ b/types/wlr_input_method_v2.c @@ -575,7 +575,7 @@ static void manager_get_input_method(struct wl_client *client, wl_resource_set_user_data(im_resource, input_method); wl_list_insert(&im_manager->input_methods, wl_resource_get_link(input_method->resource)); - wl_signal_emit_mutable(&im_manager->events.input_method, input_method); + wl_signal_emit_mutable(&im_manager->events.new_input_method, input_method); } static void manager_destroy(struct wl_client *client, @@ -608,7 +608,7 @@ static void handle_display_destroy(struct wl_listener *listener, void *data) { wl_container_of(listener, manager, display_destroy); wl_signal_emit_mutable(&manager->events.destroy, manager); - assert(wl_list_empty(&manager->events.input_method.listener_list)); + assert(wl_list_empty(&manager->events.new_input_method.listener_list)); assert(wl_list_empty(&manager->events.destroy.listener_list)); wl_list_remove(&manager->display_destroy.link); @@ -623,7 +623,7 @@ struct wlr_input_method_manager_v2 *wlr_input_method_manager_v2_create( return NULL; } - wl_signal_init(&im_manager->events.input_method); + wl_signal_init(&im_manager->events.new_input_method); wl_signal_init(&im_manager->events.destroy); wl_list_init(&im_manager->input_methods); From 102a6bd415d4b4071d96bffdfc7c92626eacf3b3 Mon Sep 17 00:00:00 2001 From: tokyo4j Date: Mon, 7 Jul 2025 05:59:40 +0900 Subject: [PATCH 109/311] input-method: use `NULL` when emitting signals --- include/wlr/types/wlr_input_method_v2.h | 6 +++--- types/wlr_input_method_v2.c | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/include/wlr/types/wlr_input_method_v2.h b/include/wlr/types/wlr_input_method_v2.h index 885b3fe80..aaafbf5f2 100644 --- a/include/wlr/types/wlr_input_method_v2.h +++ b/include/wlr/types/wlr_input_method_v2.h @@ -48,10 +48,10 @@ struct wlr_input_method_v2 { struct wl_list link; struct { - struct wl_signal commit; // struct wlr_input_method_v2 + struct wl_signal commit; struct wl_signal new_popup_surface; // struct wlr_input_popup_surface_v2 struct wl_signal grab_keyboard; // struct wlr_input_method_keyboard_grab_v2 - struct wl_signal destroy; // struct wlr_input_method_v2 + struct wl_signal destroy; } events; struct { @@ -95,7 +95,7 @@ struct wlr_input_method_manager_v2 { struct { struct wl_signal new_input_method; // struct wlr_input_method_v2 - struct wl_signal destroy; // struct wlr_input_method_manager_v2 + struct wl_signal destroy; } events; struct { diff --git a/types/wlr_input_method_v2.c b/types/wlr_input_method_v2.c index ed2a4ebeb..0a521df48 100644 --- a/types/wlr_input_method_v2.c +++ b/types/wlr_input_method_v2.c @@ -56,7 +56,7 @@ static void input_method_destroy(struct wlr_input_method_v2 *input_method) { popup_surface, tmp, &input_method->popup_surfaces, link) { popup_surface_destroy(popup_surface); } - wl_signal_emit_mutable(&input_method->events.destroy, input_method); + wl_signal_emit_mutable(&input_method->events.destroy, NULL); assert(wl_list_empty(&input_method->events.commit.listener_list)); assert(wl_list_empty(&input_method->events.new_popup_surface.listener_list)); @@ -102,7 +102,7 @@ static void im_commit(struct wl_client *client, struct wl_resource *resource, input_method->current = input_method->pending; input_method->pending = (struct wlr_input_method_v2_state){0}; - wl_signal_emit_mutable(&input_method->events.commit, input_method); + wl_signal_emit_mutable(&input_method->events.commit, NULL); } static void im_commit_string(struct wl_client *client, @@ -606,7 +606,7 @@ static void input_method_manager_bind(struct wl_client *wl_client, void *data, static void handle_display_destroy(struct wl_listener *listener, void *data) { struct wlr_input_method_manager_v2 *manager = wl_container_of(listener, manager, display_destroy); - wl_signal_emit_mutable(&manager->events.destroy, manager); + wl_signal_emit_mutable(&manager->events.destroy, NULL); assert(wl_list_empty(&manager->events.new_input_method.listener_list)); assert(wl_list_empty(&manager->events.destroy.listener_list)); From 905465b0fa5e64cb966afda7d2e5d3c04a0438d2 Mon Sep 17 00:00:00 2001 From: David Turner Date: Tue, 9 Sep 2025 14:35:25 +0100 Subject: [PATCH 110/311] color-representation-v1: Actually set supported_*_len --- types/wlr_color_representation_v1.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/types/wlr_color_representation_v1.c b/types/wlr_color_representation_v1.c index ac804c60a..6590ec4e7 100644 --- a/types/wlr_color_representation_v1.c +++ b/types/wlr_color_representation_v1.c @@ -370,9 +370,11 @@ struct wlr_color_representation_manager_v1 *wlr_color_representation_manager_v1_ ok &= memdup(&manager->supported_alpha_modes, options->supported_alpha_modes, sizeof(options->supported_alpha_modes[0]) * options->supported_alpha_modes_len); + manager->supported_alpha_modes_len = options->supported_alpha_modes_len; ok &= memdup(&manager->supported_coeffs_and_ranges, options->supported_coeffs_and_ranges, sizeof(options->supported_coeffs_and_ranges[0]) * options->supported_coeffs_and_ranges_len); + manager->supported_coeffs_and_ranges_len = options->supported_coeffs_and_ranges_len; if (!ok) { goto err_options; } From cdd2c7e0064efee7fd398b2935923508b2fa9842 Mon Sep 17 00:00:00 2001 From: rewine Date: Tue, 5 Aug 2025 15:56:27 +0800 Subject: [PATCH 111/311] protocols: sync with wlr-protocols, apply non-breaking updates and doc improvements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This sync includes minor non-breaking updates from recent years: - Fix typos and grammatical issues (e.g. "a an" → "an", "inexistent" → "nonexistent") - Improve description consistency with `wl_output` (e.g. name, description, make, model, serial) - Add `destructor` annotation to relevant events (e.g. `finished` in foreign-toplevel) - Clarify event emission timing and behavior for output management - No functional or semantic protocol changes introduced These changes improve the accuracy and consistency of protocol descriptions without impacting compatibility. --- protocol/wlr-export-dmabuf-unstable-v1.xml | 4 +-- ...oreign-toplevel-management-unstable-v1.xml | 2 +- protocol/wlr-gamma-control-unstable-v1.xml | 2 +- .../wlr-output-management-unstable-v1.xml | 30 ++++++++++++------- ...lr-output-power-management-unstable-v1.xml | 4 +-- protocol/wlr-screencopy-unstable-v1.xml | 9 +++--- 6 files changed, 30 insertions(+), 21 deletions(-) diff --git a/protocol/wlr-export-dmabuf-unstable-v1.xml b/protocol/wlr-export-dmabuf-unstable-v1.xml index 751f7efbf..80ea012f5 100644 --- a/protocol/wlr-export-dmabuf-unstable-v1.xml +++ b/protocol/wlr-export-dmabuf-unstable-v1.xml @@ -43,7 +43,7 @@ - Capture the next frame of a an entire output. + Capture the next frame of an entire output. + summary="index of the plane the data in the object applies to"/> diff --git a/protocol/wlr-foreign-toplevel-management-unstable-v1.xml b/protocol/wlr-foreign-toplevel-management-unstable-v1.xml index 108133715..44505bbb6 100644 --- a/protocol/wlr-foreign-toplevel-management-unstable-v1.xml +++ b/protocol/wlr-foreign-toplevel-management-unstable-v1.xml @@ -58,7 +58,7 @@ - + This event indicates that the compositor is done sending events to the zwlr_foreign_toplevel_manager_v1. The server will destroy the object diff --git a/protocol/wlr-gamma-control-unstable-v1.xml b/protocol/wlr-gamma-control-unstable-v1.xml index a9db76240..16e0be8b1 100644 --- a/protocol/wlr-gamma-control-unstable-v1.xml +++ b/protocol/wlr-gamma-control-unstable-v1.xml @@ -72,7 +72,7 @@ tables. At any time the compositor can send a failed event indicating that this object is no longer valid. - There must always be at most one gamma control object per output, which + There can only be at most one gamma control object per output, which has exclusive access to this particular output. When the gamma control object is destroyed, the gamma table is restored to its original value. diff --git a/protocol/wlr-output-management-unstable-v1.xml b/protocol/wlr-output-management-unstable-v1.xml index 411e2f049..541284a8c 100644 --- a/protocol/wlr-output-management-unstable-v1.xml +++ b/protocol/wlr-output-management-unstable-v1.xml @@ -156,8 +156,8 @@ not assume that the name is a reflection of an underlying DRM connector, X11 connection, etc. - If the compositor implements the xdg-output protocol and this head is - enabled, the xdg_output.name event must report the same name. + If this head matches a wl_output, the wl_output.name event must report + the same name. The name event is sent after a wlr_output_head object is created. This event is only sent once per object, and the name does not change over @@ -176,8 +176,8 @@ the make, model, serial of the underlying DRM connector or the display name of the underlying X11 connection, etc. - If the compositor implements xdg-output and this head is enabled, - the xdg_output.description must report the same description. + If this head matches a wl_output, the wl_output.description event must + report the same name. The description event is sent after a wlr_output_head object is created. This event is only sent once per object, and the description does not @@ -191,6 +191,10 @@ This event describes the physical size of the head. This event is only sent if the head has a physical size (e.g. is not a projector or a virtual device). + + The physical size event is sent after a wlr_output_head object is created. This + event is only sent once per object, and the physical size does not change over + the lifetime of the wlr_output_head object. @@ -264,9 +268,6 @@ This event describes the manufacturer of the head. - This must report the same make as the wl_output interface does in its - geometry event. - Together with the model and serial_number events the purpose is to allow clients to recognize heads from previous sessions and for example load head-specific configurations back. @@ -278,6 +279,10 @@ identify the head by available information from other events but should be aware that there is an increased risk of false positives. + If sent, the make event is sent after a wlr_output_head object is + created and only sent once per object. The make does not change over + the lifetime of the wlr_output_head object. + It is not recommended to display the make string in UI to users. For that the string provided by the description event should be preferred. @@ -288,9 +293,6 @@ This event describes the model of the head. - This must report the same model as the wl_output interface does in its - geometry event. - Together with the make and serial_number events the purpose is to allow clients to recognize heads from previous sessions and for example load head-specific configurations back. @@ -302,6 +304,10 @@ identify the head by available information from other events but should be aware that there is an increased risk of false positives. + If sent, the model event is sent after a wlr_output_head object is + created and only sent once per object. The model does not change over + the lifetime of the wlr_output_head object. + It is not recommended to display the model string in UI to users. For that the string provided by the description event should be preferred. @@ -323,6 +329,10 @@ available information from other events but should be aware that there is an increased risk of false positives. + If sent, the serial number event is sent after a wlr_output_head object + is created and only sent once per object. The serial number does not + change over the lifetime of the wlr_output_head object. + It is not recommended to display the serial_number string in UI to users. For that the string provided by the description event should be preferred. diff --git a/protocol/wlr-output-power-management-unstable-v1.xml b/protocol/wlr-output-power-management-unstable-v1.xml index a97783991..20dbb7760 100644 --- a/protocol/wlr-output-power-management-unstable-v1.xml +++ b/protocol/wlr-output-power-management-unstable-v1.xml @@ -50,7 +50,7 @@ - Create a output power management mode control that can be used to + Create an output power management mode control that can be used to adjust the power management mode for a given output. @@ -79,7 +79,7 @@ - + diff --git a/protocol/wlr-screencopy-unstable-v1.xml b/protocol/wlr-screencopy-unstable-v1.xml index 50b1b7d2a..85b57d7ea 100644 --- a/protocol/wlr-screencopy-unstable-v1.xml +++ b/protocol/wlr-screencopy-unstable-v1.xml @@ -88,7 +88,7 @@ supported buffer type. The "buffer_done" event is sent afterwards to indicate that all supported buffer types have been enumerated. The client will then be able to send a "copy" request. If the capture is successful, - the compositor will send a "flags" followed by a "ready" event. + the compositor will send a "flags" event followed by a "ready" event. For objects version 2 or lower, wl_shm buffers are always supported, ie. the "buffer" event is guaranteed to be sent. @@ -114,12 +114,12 @@ - Copy the frame to the supplied buffer. The buffer must have a the + Copy the frame to the supplied buffer. The buffer must have the correct size, see zwlr_screencopy_frame_v1.buffer and zwlr_screencopy_frame_v1.linux_dmabuf. The buffer needs to have a supported format. - If the frame is successfully copied, a "flags" and a "ready" events are + If the frame is successfully copied, "flags" and "ready" events are sent. Otherwise, a "failed" event is sent. @@ -147,8 +147,7 @@ Called as soon as the frame is copied, indicating it is available - for reading. This event includes the time at which presentation happened - at. + for reading. This event includes the time at which the presentation took place. The timestamp is expressed as tv_sec_hi, tv_sec_lo, tv_nsec triples, each component being an unsigned 32-bit value. Whole seconds are in From 5e5842cb1a8e11a7f274638e124ca473025734bc Mon Sep 17 00:00:00 2001 From: liupeng Date: Sat, 30 Aug 2025 14:42:14 +0800 Subject: [PATCH 112/311] drm_lease_v1: initialize device resource link during abnormal exit Signed-off-by: liupeng --- types/wlr_drm_lease_v1.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/types/wlr_drm_lease_v1.c b/types/wlr_drm_lease_v1.c index 892a401b3..14846f21d 100644 --- a/types/wlr_drm_lease_v1.c +++ b/types/wlr_drm_lease_v1.c @@ -453,10 +453,12 @@ static void lease_device_bind(struct wl_client *wl_client, void *data, if (!device) { wlr_log(WLR_DEBUG, "Failed to bind lease device, " "the wlr_drm_lease_device_v1 has been destroyed"); + wl_list_init(wl_resource_get_link(device_resource)); return; } wl_resource_set_user_data(device_resource, device); + wl_list_insert(&device->resources, wl_resource_get_link(device_resource)); int fd = wlr_drm_backend_get_non_master_fd(device->backend); if (fd < 0) { @@ -468,8 +470,6 @@ static void lease_device_bind(struct wl_client *wl_client, void *data, wp_drm_lease_device_v1_send_drm_fd(device_resource, fd); close(fd); - wl_list_insert(&device->resources, wl_resource_get_link(device_resource)); - struct wlr_drm_lease_connector_v1 *connector; wl_list_for_each(connector, &device->connectors, link) { drm_lease_connector_v1_send_to_client(connector, device_resource); From fd069ad4f2793c812bd47e40f52f92eb8af7ca34 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Thu, 7 Aug 2025 11:56:49 +0200 Subject: [PATCH 113/311] output/cursor: fix missing second cursor When attaching more than one cursor to wlr_output, the first one will pick the output's hardware cursor, then for the second one output_set_hardware_cursor() would fail (since the hardware cursor was already taken), but we still ended up resetting the current hardware cursor (by calling output_disable_hardware_cursor() below). As a result only the second cursor would be displayed. To fix this, move the current hardware cursor check to the caller. Fixes: 510664e79bfc ("output: disable hardware cursor when falling back to software") --- types/output/cursor.c | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/types/output/cursor.c b/types/output/cursor.c index b3ec152ce..70647afb7 100644 --- a/types/output/cursor.c +++ b/types/output/cursor.c @@ -288,13 +288,7 @@ static struct wlr_buffer *render_cursor_buffer(struct wlr_output_cursor *cursor) static bool output_cursor_attempt_hardware(struct wlr_output_cursor *cursor) { struct wlr_output *output = cursor->output; - if (!output->impl->set_cursor || - output->software_cursor_locks > 0) { - return false; - } - - struct wlr_output_cursor *hwcur = output->hardware_cursor; - if (hwcur != NULL && hwcur != cursor) { + if (!output->impl->set_cursor || output->software_cursor_locks > 0) { return false; } @@ -422,12 +416,15 @@ bool output_cursor_set_texture(struct wlr_output_cursor *cursor, wl_list_init(&cursor->renderer_destroy.link); } - if (output_cursor_attempt_hardware(cursor)) { - return true; + if (output->hardware_cursor == NULL || output->hardware_cursor == cursor) { + if (output_cursor_attempt_hardware(cursor)) { + return true; + } + + wlr_log(WLR_DEBUG, "Falling back to software cursor on output '%s'", output->name); + output_disable_hardware_cursor(output); } - wlr_log(WLR_DEBUG, "Falling back to software cursor on output '%s'", output->name); - output_disable_hardware_cursor(output); output_cursor_damage_whole(cursor); return true; } From b62c6878e116015d34827f66a8c4fee986e4ebfc Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sun, 3 Aug 2025 17:25:05 +0200 Subject: [PATCH 114/311] scene/surface: simplify single-pixel-buffer check in surface_reconfigure() No need to call wlr_client_buffer_get() on wlr_client_buffer.base: we're already manipulating a wlr_client_buffer. --- types/scene/surface.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/types/scene/surface.c b/types/scene/surface.c index 7653323e3..e1c424ccd 100644 --- a/types/scene/surface.c +++ b/types/scene/surface.c @@ -217,10 +217,9 @@ static void surface_reconfigure(struct wlr_scene_surface *scene_surface) { // can't use the cached scene_buffer->is_single_pixel_buffer // because that's only set later on. bool is_single_pixel_buffer = false; - struct wlr_client_buffer *client_buffer = wlr_client_buffer_get(&surface->buffer->base); - if (client_buffer != NULL && client_buffer->source != NULL) { + if (surface->buffer->source != NULL) { struct wlr_single_pixel_buffer_v1 *spb = - wlr_single_pixel_buffer_v1_try_from_buffer(client_buffer->source); + wlr_single_pixel_buffer_v1_try_from_buffer(surface->buffer->source); is_single_pixel_buffer = spb != NULL; } if (!is_single_pixel_buffer) { From bd566225eacdda2b72b967fb5168314924871052 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sun, 3 Aug 2025 17:28:13 +0200 Subject: [PATCH 115/311] scene/surface: fix NULL deref when source buffer is destroyed Fixes the following crash, witnessed after a GPU reset: #0 0x00007fba9a32774c n/a (libc.so.6 + 0x9774c) #1 0x00007fba9a2cddc0 raise (libc.so.6 + 0x3ddc0) #2 0x00007fba9a2b557a abort (libc.so.6 + 0x2557a) #3 0x00007fba9a2b54e3 n/a (libc.so.6 + 0x254e3) #4 0x00007fba9a53fb78 wlr_linux_drm_syncobj_v1_state_signal_release_with_buffer (libwlroots-0.20.so + 0x26b78) #5 0x00007fba9a590846 surface_reconfigure (libwlroots-0.20.so + 0x77846) #6 0x00007fba9a590cbb scene_surface_set_clip (libwlroots-0.20.so + 0x77cbb) #7 0x00007fba9a590efa subsurface_tree_set_clip (libwlroots-0.20.so + 0x77efa) #8 0x00007fba9a590f1f subsurface_tree_set_clip (libwlroots-0.20.so + 0x77f1f) #9 0x00007fba9a590f1f subsurface_tree_set_clip (libwlroots-0.20.so + 0x77f1f) #10 0x00007fba9a590f8d wlr_scene_subsurface_tree_set_clip (libwlroots-0.20.so + 0x77f8d) Reported-by: Hubert Hirtz --- types/scene/surface.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/types/scene/surface.c b/types/scene/surface.c index e1c424ccd..135ded6fd 100644 --- a/types/scene/surface.c +++ b/types/scene/surface.c @@ -245,7 +245,8 @@ static void surface_reconfigure(struct wlr_scene_surface *scene_surface) { &surface->buffer->base, &options); if (syncobj_surface_state != NULL && - (surface->current.committed & WLR_SURFACE_STATE_BUFFER)) { + (surface->current.committed & WLR_SURFACE_STATE_BUFFER) && + surface->buffer->source != NULL) { wlr_linux_drm_syncobj_v1_state_signal_release_with_buffer(syncobj_surface_state, surface->buffer->source); } From 462046ffdcdaacfc38ac606ed74397c360e23606 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sun, 3 Aug 2025 17:30:07 +0200 Subject: [PATCH 116/311] cursor: use source buffer to signal release timeline point Same as 128cd07e9156 ("scene/surface: use source buffer to signal release timeline point"), but for the cursor. --- types/wlr_cursor.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/types/wlr_cursor.c b/types/wlr_cursor.c index 6dad11446..5f4aac398 100644 --- a/types/wlr_cursor.c +++ b/types/wlr_cursor.c @@ -585,10 +585,11 @@ static void cursor_output_cursor_update(struct wlr_cursor_output_cursor *output_ &src_box, dst_width, dst_height, surface->current.transform, hotspot_x, hotspot_y, wait_timeline, wait_point); - if (syncobj_surface_state != NULL && surface->buffer != NULL && + if (syncobj_surface_state != NULL && + surface->buffer != NULL && surface->buffer->source != NULL && (surface->current.committed & WLR_SURFACE_STATE_BUFFER)) { wlr_linux_drm_syncobj_v1_state_signal_release_with_buffer(syncobj_surface_state, - &surface->buffer->base); + surface->buffer->source); } if (output_cursor->output_cursor->visible) { From d7ae9a866bf2372e0e86a9cf51a963b4fbc30c08 Mon Sep 17 00:00:00 2001 From: JiDe Zhang Date: Mon, 15 Sep 2025 15:58:51 +0800 Subject: [PATCH 117/311] xwayland: fix assertion failure in wlr_xwayland_shell_v1 The issue occurred when `wlr_xwayland_shell_v1` was destroyed before `wlr_xwayland`. This happened because `wlr_xwayland` didn't remove the listener for the shell's destroy event in `handle_shell_destroy`. --- xwayland/xwayland.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/xwayland/xwayland.c b/xwayland/xwayland.c index d25a132b5..5d51df074 100644 --- a/xwayland/xwayland.c +++ b/xwayland/xwayland.c @@ -69,6 +69,11 @@ static void handle_shell_destroy(struct wl_listener *listener, void *data) { struct wlr_xwayland *xwayland = wl_container_of(listener, xwayland, shell_destroy); xwayland->shell_v1 = NULL; + wl_list_remove(&xwayland->shell_destroy.link); + // Will remove this list in handle_shell_destroy(). + // This ensures the link is always initialized and + // avoids the need to keep check conditions in sync. + wl_list_init(&xwayland->shell_destroy.link); } void wlr_xwayland_destroy(struct wlr_xwayland *xwayland) { From dd7f5431891cad6b3980c8a7d5fdc1c1f668c1b1 Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Mon, 8 Sep 2025 15:40:55 +0200 Subject: [PATCH 118/311] render/vulkan: Handle multi-descriptor sets A combined image sampler may need several descriptors in a descriptor set. We are not currently checking how many descriptors are required, nor is it presumably guaranteed that such multi-descriptor allocation will not fail due to fragmentation. If the pool free counter is not zero, try to allocate but continue with the next pool and fall back to creating a new pool if the allocation failed. Fixes: https://gitlab.freedesktop.org/wlroots/wlroots/-/issues/4010 --- render/vulkan/renderer.c | 109 ++++++++++++++++++++++----------------- 1 file changed, 61 insertions(+), 48 deletions(-) diff --git a/render/vulkan/renderer.c b/render/vulkan/renderer.c index 08d99bb7c..f80435c99 100644 --- a/render/vulkan/renderer.c +++ b/render/vulkan/renderer.c @@ -66,59 +66,72 @@ static struct wlr_vk_descriptor_pool *alloc_ds( struct wl_list *pool_list, size_t *last_pool_size) { VkResult res; - bool found = false; - struct wlr_vk_descriptor_pool *pool; - wl_list_for_each(pool, pool_list, link) { - if (pool->free > 0) { - found = true; - break; - } - } - - if (!found) { // create new pool - pool = calloc(1, sizeof(*pool)); - if (!pool) { - wlr_log_errno(WLR_ERROR, "allocation failed"); - return NULL; - } - - size_t count = 2 * (*last_pool_size); - if (!count) { - count = start_descriptor_pool_size; - } - - pool->free = count; - VkDescriptorPoolSize pool_size = { - .descriptorCount = count, - .type = type, - }; - - VkDescriptorPoolCreateInfo dpool_info = { - .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, - .maxSets = count, - .poolSizeCount = 1, - .pPoolSizes = &pool_size, - .flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, - }; - - res = vkCreateDescriptorPool(renderer->dev->dev, &dpool_info, NULL, - &pool->pool); - if (res != VK_SUCCESS) { - wlr_vk_error("vkCreateDescriptorPool", res); - free(pool); - return NULL; - } - - *last_pool_size = count; - wl_list_insert(pool_list, &pool->link); - } - VkDescriptorSetAllocateInfo ds_info = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, .descriptorSetCount = 1, .pSetLayouts = layout, - .descriptorPool = pool->pool, }; + + struct wlr_vk_descriptor_pool *pool; + wl_list_for_each(pool, pool_list, link) { + if (pool->free > 0) { + ds_info.descriptorPool = pool->pool; + res = vkAllocateDescriptorSets(renderer->dev->dev, &ds_info, ds); + switch (res) { + case VK_ERROR_FRAGMENTED_POOL: + case VK_ERROR_OUT_OF_POOL_MEMORY: + // Descriptor sets with more than one descriptor can cause us + // to run out of pool memory early or lead to fragmentation + // that makes the pool unable to service our allocation + // request. Try the next pool or allocate a new one. + continue; + case VK_SUCCESS: + --pool->free; + return pool; + default: + wlr_vk_error("vkAllocateDescriptorSets", res); + return NULL; + } + } + } + + pool = calloc(1, sizeof(*pool)); + if (!pool) { + wlr_log_errno(WLR_ERROR, "allocation failed"); + return NULL; + } + + size_t count = 2 * (*last_pool_size); + if (!count) { + count = start_descriptor_pool_size; + } + + pool->free = count; + VkDescriptorPoolSize pool_size = { + .descriptorCount = count, + .type = type, + }; + + VkDescriptorPoolCreateInfo dpool_info = { + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, + .maxSets = count, + .poolSizeCount = 1, + .pPoolSizes = &pool_size, + .flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, + }; + + res = vkCreateDescriptorPool(renderer->dev->dev, &dpool_info, NULL, + &pool->pool); + if (res != VK_SUCCESS) { + wlr_vk_error("vkCreateDescriptorPool", res); + free(pool); + return NULL; + } + + *last_pool_size = count; + wl_list_insert(pool_list, &pool->link); + + ds_info.descriptorPool = pool->pool; res = vkAllocateDescriptorSets(renderer->dev->dev, &ds_info, ds); if (res != VK_SUCCESS) { wlr_vk_error("vkAllocateDescriptorSets", res); From 54374b6fe69ab2f481ab83d54a38e355b63f047f Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sat, 21 Jun 2025 16:57:51 +0200 Subject: [PATCH 119/311] render/vulkan: rename plain to two_pass We will introduce a new subpass without any post-processing step. Rename "plain" so that there's no confusion. --- include/render/vulkan.h | 4 +-- render/vulkan/pass.c | 32 ++++++++++----------- render/vulkan/renderer.c | 60 ++++++++++++++++++++-------------------- 3 files changed, 48 insertions(+), 48 deletions(-) diff --git a/include/render/vulkan.h b/include/render/vulkan.h index deff0eac3..52be0ba3d 100644 --- a/include/render/vulkan.h +++ b/include/render/vulkan.h @@ -241,10 +241,10 @@ struct wlr_vk_render_buffer { VkDescriptorSet blend_descriptor_set; struct wlr_vk_descriptor_pool *blend_attachment_pool; bool blend_transitioned; - } plain; + } two_pass; }; -bool vulkan_setup_plain_framebuffer(struct wlr_vk_render_buffer *buffer, +bool vulkan_setup_two_pass_framebuffer(struct wlr_vk_render_buffer *buffer, const struct wlr_dmabuf_attributes *dmabuf); struct wlr_vk_command_buffer { diff --git a/render/vulkan/pass.c b/render/vulkan/pass.c index 4fd357113..b35ab257d 100644 --- a/render/vulkan/pass.c +++ b/render/vulkan/pass.c @@ -227,7 +227,7 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { VkPipeline pipeline = VK_NULL_HANDLE; if (pass->color_transform && pass->color_transform->type != COLOR_TRANSFORM_INVERSE_EOTF) { - pipeline = render_buffer->plain.render_setup->output_pipe_lut3d; + pipeline = render_buffer->two_pass.render_setup->output_pipe_lut3d; } else { enum wlr_color_transfer_function tf = WLR_COLOR_TRANSFER_FUNCTION_SRGB; if (pass->color_transform && pass->color_transform->type == COLOR_TRANSFORM_INVERSE_EOTF) { @@ -238,13 +238,13 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { switch (tf) { case WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR: - pipeline = render_buffer->plain.render_setup->output_pipe_identity; + pipeline = render_buffer->two_pass.render_setup->output_pipe_identity; break; case WLR_COLOR_TRANSFER_FUNCTION_SRGB: - pipeline = render_buffer->plain.render_setup->output_pipe_srgb; + pipeline = render_buffer->two_pass.render_setup->output_pipe_srgb; break; case WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ: - pipeline = render_buffer->plain.render_setup->output_pipe_pq; + pipeline = render_buffer->two_pass.render_setup->output_pipe_pq; break; } @@ -268,7 +268,7 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { lut_ds = renderer->output_ds_lut3d_dummy; } VkDescriptorSet ds[] = { - render_buffer->plain.blend_descriptor_set, // set 0 + render_buffer->two_pass.blend_descriptor_set, // set 0 lut_ds, // set 1 }; size_t ds_len = sizeof(ds) / sizeof(ds[0]); @@ -404,24 +404,24 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { render_buffer->srgb.transitioned = true; } } else { - if (!render_buffer->plain.transitioned) { + if (!render_buffer->two_pass.transitioned) { src_layout = VK_IMAGE_LAYOUT_PREINITIALIZED; - render_buffer->plain.transitioned = true; + render_buffer->two_pass.transitioned = true; } // The render pass changes the blend image layout from // color attachment to read only, so on each frame, before // the render pass starts, we change it back VkImageLayout blend_src_layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; - if (!render_buffer->plain.blend_transitioned) { + if (!render_buffer->two_pass.blend_transitioned) { blend_src_layout = VK_IMAGE_LAYOUT_UNDEFINED; - render_buffer->plain.blend_transitioned = true; + render_buffer->two_pass.blend_transitioned = true; } VkImageMemoryBarrier blend_acq_barrier = { .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = render_buffer->plain.blend_image, + .image = render_buffer->two_pass.blend_image, .oldLayout = blend_src_layout, .newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, .srcAccessMask = VK_ACCESS_SHADER_READ_BIT, @@ -680,7 +680,7 @@ static void render_pass_add_rect(struct wlr_render_pass *wlr_pass, struct wlr_vk_render_format_setup *setup = pass->srgb_pathway ? pass->render_buffer->srgb.render_setup : - pass->render_buffer->plain.render_setup; + pass->render_buffer->two_pass.render_setup; struct wlr_vk_pipeline *pipe = setup_get_or_create_pipeline( setup, &(struct wlr_vk_pipeline_key) { @@ -807,7 +807,7 @@ static void render_pass_add_texture(struct wlr_render_pass *wlr_pass, struct wlr_vk_render_format_setup *setup = pass->srgb_pathway ? pass->render_buffer->srgb.render_setup : - pass->render_buffer->plain.render_setup; + pass->render_buffer->two_pass.render_setup; struct wlr_vk_pipeline *pipe = setup_get_or_create_pipeline( setup, &(struct wlr_vk_pipeline_key) { @@ -1193,10 +1193,10 @@ struct wlr_vk_render_pass *vulkan_begin_render_pass(struct wlr_vk_renderer *rend using_srgb_pathway = buffer->srgb.framebuffer != VK_NULL_HANDLE; } - if (!using_srgb_pathway && !buffer->plain.image_view) { + if (!using_srgb_pathway && !buffer->two_pass.image_view) { struct wlr_dmabuf_attributes attribs; wlr_buffer_get_dmabuf(buffer->wlr_buffer, &attribs); - if (!vulkan_setup_plain_framebuffer(buffer, &attribs)) { + if (!vulkan_setup_two_pass_framebuffer(buffer, &attribs)) { wlr_log(WLR_ERROR, "Failed to set up blend image"); return NULL; } @@ -1262,8 +1262,8 @@ struct wlr_vk_render_pass *vulkan_begin_render_pass(struct wlr_vk_renderer *rend rp_info.renderPass = buffer->srgb.render_setup->render_pass; rp_info.framebuffer = buffer->srgb.framebuffer; } else { - rp_info.renderPass = buffer->plain.render_setup->render_pass; - rp_info.framebuffer = buffer->plain.framebuffer; + rp_info.renderPass = buffer->two_pass.render_setup->render_pass; + rp_info.framebuffer = buffer->two_pass.framebuffer; } vkCmdBeginRenderPass(cb->vk, &rp_info, VK_SUBPASS_CONTENTS_INLINE); diff --git a/render/vulkan/renderer.c b/render/vulkan/renderer.c index f80435c99..62168d979 100644 --- a/render/vulkan/renderer.c +++ b/render/vulkan/renderer.c @@ -620,14 +620,14 @@ static void destroy_render_buffer(struct wlr_vk_render_buffer *buffer) { vkDestroyFramebuffer(dev, buffer->srgb.framebuffer, NULL); vkDestroyImageView(dev, buffer->srgb.image_view, NULL); - vkDestroyFramebuffer(dev, buffer->plain.framebuffer, NULL); - vkDestroyImageView(dev, buffer->plain.image_view, NULL); - vkDestroyImage(dev, buffer->plain.blend_image, NULL); - vkFreeMemory(dev, buffer->plain.blend_memory, NULL); - vkDestroyImageView(dev, buffer->plain.blend_image_view, NULL); - if (buffer->plain.blend_attachment_pool) { - vulkan_free_ds(buffer->renderer, buffer->plain.blend_attachment_pool, - buffer->plain.blend_descriptor_set); + vkDestroyFramebuffer(dev, buffer->two_pass.framebuffer, NULL); + vkDestroyImageView(dev, buffer->two_pass.image_view, NULL); + vkDestroyImage(dev, buffer->two_pass.blend_image, NULL); + vkFreeMemory(dev, buffer->two_pass.blend_memory, NULL); + vkDestroyImageView(dev, buffer->two_pass.blend_image_view, NULL); + if (buffer->two_pass.blend_attachment_pool) { + vulkan_free_ds(buffer->renderer, buffer->two_pass.blend_attachment_pool, + buffer->two_pass.blend_descriptor_set); } vkDestroyImage(dev, buffer->image, NULL); @@ -648,7 +648,7 @@ static struct wlr_addon_interface render_buffer_addon_impl = { .destroy = handle_render_buffer_destroy, }; -bool vulkan_setup_plain_framebuffer(struct wlr_vk_render_buffer *buffer, +bool vulkan_setup_two_pass_framebuffer(struct wlr_vk_render_buffer *buffer, const struct wlr_dmabuf_attributes *dmabuf) { struct wlr_vk_renderer *renderer = buffer->renderer; VkResult res; @@ -676,15 +676,15 @@ bool vulkan_setup_plain_framebuffer(struct wlr_vk_render_buffer *buffer, }, }; - res = vkCreateImageView(dev, &view_info, NULL, &buffer->plain.image_view); + res = vkCreateImageView(dev, &view_info, NULL, &buffer->two_pass.image_view); if (res != VK_SUCCESS) { wlr_vk_error("vkCreateImageView failed", res); goto error; } - buffer->plain.render_setup = find_or_create_render_setup( + buffer->two_pass.render_setup = find_or_create_render_setup( renderer, &fmt->format, true); - if (!buffer->plain.render_setup) { + if (!buffer->two_pass.render_setup) { goto error; } @@ -704,14 +704,14 @@ bool vulkan_setup_plain_framebuffer(struct wlr_vk_render_buffer *buffer, .usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT, }; - res = vkCreateImage(dev, &img_info, NULL, &buffer->plain.blend_image); + res = vkCreateImage(dev, &img_info, NULL, &buffer->two_pass.blend_image); if (res != VK_SUCCESS) { wlr_vk_error("vkCreateImage failed", res); goto error; } VkMemoryRequirements mem_reqs; - vkGetImageMemoryRequirements(dev, buffer->plain.blend_image, &mem_reqs); + vkGetImageMemoryRequirements(dev, buffer->two_pass.blend_image, &mem_reqs); int mem_type_index = vulkan_find_mem_type(renderer->dev, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, mem_reqs.memoryTypeBits); @@ -726,13 +726,13 @@ bool vulkan_setup_plain_framebuffer(struct wlr_vk_render_buffer *buffer, .memoryTypeIndex = mem_type_index, }; - res = vkAllocateMemory(dev, &mem_info, NULL, &buffer->plain.blend_memory); + res = vkAllocateMemory(dev, &mem_info, NULL, &buffer->two_pass.blend_memory); if (res != VK_SUCCESS) { wlr_vk_error("vkAllocatorMemory failed", res); goto error; } - res = vkBindImageMemory(dev, buffer->plain.blend_image, buffer->plain.blend_memory, 0); + res = vkBindImageMemory(dev, buffer->two_pass.blend_image, buffer->two_pass.blend_memory, 0); if (res != VK_SUCCESS) { wlr_vk_error("vkBindMemory failed", res); goto error; @@ -740,7 +740,7 @@ bool vulkan_setup_plain_framebuffer(struct wlr_vk_render_buffer *buffer, VkImageViewCreateInfo blend_view_info = { .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, - .image = buffer->plain.blend_image, + .image = buffer->two_pass.blend_image, .viewType = VK_IMAGE_VIEW_TYPE_2D, .format = img_info.format, .components.r = VK_COMPONENT_SWIZZLE_IDENTITY, @@ -756,37 +756,37 @@ bool vulkan_setup_plain_framebuffer(struct wlr_vk_render_buffer *buffer, }, }; - res = vkCreateImageView(dev, &blend_view_info, NULL, &buffer->plain.blend_image_view); + res = vkCreateImageView(dev, &blend_view_info, NULL, &buffer->two_pass.blend_image_view); if (res != VK_SUCCESS) { wlr_vk_error("vkCreateImageView failed", res); goto error; } - buffer->plain.blend_attachment_pool = vulkan_alloc_blend_ds(renderer, - &buffer->plain.blend_descriptor_set); - if (!buffer->plain.blend_attachment_pool) { + buffer->two_pass.blend_attachment_pool = vulkan_alloc_blend_ds(renderer, + &buffer->two_pass.blend_descriptor_set); + if (!buffer->two_pass.blend_attachment_pool) { wlr_log(WLR_ERROR, "failed to allocate descriptor"); goto error; } VkDescriptorImageInfo ds_attach_info = { .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, - .imageView = buffer->plain.blend_image_view, + .imageView = buffer->two_pass.blend_image_view, .sampler = VK_NULL_HANDLE, }; VkWriteDescriptorSet ds_write = { .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .descriptorCount = 1, .descriptorType = VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, - .dstSet = buffer->plain.blend_descriptor_set, + .dstSet = buffer->two_pass.blend_descriptor_set, .dstBinding = 0, .pImageInfo = &ds_attach_info, }; vkUpdateDescriptorSets(dev, 1, &ds_write, 0, NULL); VkImageView attachments[] = { - buffer->plain.blend_image_view, - buffer->plain.image_view + buffer->two_pass.blend_image_view, + buffer->two_pass.image_view, }; VkFramebufferCreateInfo fb_info = { .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, @@ -796,10 +796,10 @@ bool vulkan_setup_plain_framebuffer(struct wlr_vk_render_buffer *buffer, .width = dmabuf->width, .height = dmabuf->height, .layers = 1u, - .renderPass = buffer->plain.render_setup->render_pass, + .renderPass = buffer->two_pass.render_setup->render_pass, }; - res = vkCreateFramebuffer(dev, &fb_info, NULL, &buffer->plain.framebuffer); + res = vkCreateFramebuffer(dev, &fb_info, NULL, &buffer->two_pass.framebuffer); if (res != VK_SUCCESS) { wlr_vk_error("vkCreateFramebuffer", res); goto error; @@ -824,7 +824,7 @@ static bool vulkan_setup_srgb_framebuffer(struct wlr_vk_render_buffer *buffer, assert(fmt); assert(fmt->format.vk_srgb); - // Set up the srgb framebuffer by default; plain framebuffer and + // Set up the srgb framebuffer by default; two-pass framebuffer and // blending image will be set up later if necessary VkImageViewCreateInfo view_info = { .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, @@ -920,8 +920,8 @@ static struct wlr_vk_render_buffer *create_render_buffer( goto error; } } else { - // Set up the plain framebuffer & blending image - if (!vulkan_setup_plain_framebuffer(buffer, &dmabuf)) { + // Set up the two-pass framebuffer & blending image + if (!vulkan_setup_two_pass_framebuffer(buffer, &dmabuf)) { goto error; } } From 7f6d66ea62c88e788421970f6e7ff6dd252f4f46 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sat, 21 Jun 2025 17:07:00 +0200 Subject: [PATCH 120/311] render/vulkan: use sRGB image view when color transform is set If the color transform is set to sRGB inverse EOTF, we can use the sRGB image view just like when no color transform is passed in. --- render/vulkan/pass.c | 39 ++++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/render/vulkan/pass.c b/render/vulkan/pass.c index b35ab257d..646e3cab6 100644 --- a/render/vulkan/pass.c +++ b/render/vulkan/pass.c @@ -1177,28 +1177,41 @@ static const struct wlr_addon_interface vk_color_transform_impl = { struct wlr_vk_render_pass *vulkan_begin_render_pass(struct wlr_vk_renderer *renderer, struct wlr_vk_render_buffer *buffer, const struct wlr_buffer_pass_options *options) { - bool using_srgb_pathway; + uint32_t inv_eotf; if (options != NULL && options->color_transform != NULL) { - using_srgb_pathway = false; + if (options->color_transform->type == COLOR_TRANSFORM_INVERSE_EOTF) { + struct wlr_color_transform_inverse_eotf *tr = + wlr_color_transform_inverse_eotf_from_base(options->color_transform); + inv_eotf = tr->tf; + } else { + // Color transform is not an inverse EOTF + inv_eotf = 0; + } + } else { + // This is the default when unspecified + inv_eotf = WLR_COLOR_TRANSFER_FUNCTION_SRGB; + } - if (!get_color_transform(options->color_transform, renderer)) { + bool using_srgb_pathway = inv_eotf == WLR_COLOR_TRANSFER_FUNCTION_SRGB && + buffer->srgb.framebuffer != VK_NULL_HANDLE; + + if (!using_srgb_pathway) { + if (options != NULL && options->color_transform != NULL && + !get_color_transform(options->color_transform, renderer)) { /* Try to create a new color transform */ if (!vk_color_transform_create(renderer, options->color_transform)) { wlr_log(WLR_ERROR, "Failed to create color transform"); return NULL; } } - } else { - // Use srgb pathway if it is the default/has already been set up - using_srgb_pathway = buffer->srgb.framebuffer != VK_NULL_HANDLE; - } - if (!using_srgb_pathway && !buffer->two_pass.image_view) { - struct wlr_dmabuf_attributes attribs; - wlr_buffer_get_dmabuf(buffer->wlr_buffer, &attribs); - if (!vulkan_setup_two_pass_framebuffer(buffer, &attribs)) { - wlr_log(WLR_ERROR, "Failed to set up blend image"); - return NULL; + if (!buffer->two_pass.image_view) { + struct wlr_dmabuf_attributes attribs; + wlr_buffer_get_dmabuf(buffer->wlr_buffer, &attribs); + if (!vulkan_setup_two_pass_framebuffer(buffer, &attribs)) { + wlr_log(WLR_ERROR, "Failed to set up blend image"); + return NULL; + } } } From 6fee3623e44bbfaf7588431ee87c1f85c8de9fcb Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sat, 21 Jun 2025 17:18:26 +0200 Subject: [PATCH 121/311] render/vulkan: rename vulkan_setup_srgb_framebuffer() for linear Rename to "one-pass" (to indicate no blending buffer is involved), because this will get re-used when introducing a linear single-subpass codepath. --- render/vulkan/renderer.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/render/vulkan/renderer.c b/render/vulkan/renderer.c index 62168d979..4f69c0c94 100644 --- a/render/vulkan/renderer.c +++ b/render/vulkan/renderer.c @@ -813,7 +813,7 @@ error: return false; } -static bool vulkan_setup_srgb_framebuffer(struct wlr_vk_render_buffer *buffer, +static bool vulkan_setup_one_pass_framebuffer(struct wlr_vk_render_buffer *buffer, const struct wlr_dmabuf_attributes *dmabuf) { struct wlr_vk_renderer *renderer = buffer->renderer; VkResult res; @@ -916,7 +916,7 @@ static struct wlr_vk_render_buffer *create_render_buffer( } if (using_mutable_srgb) { - if (!vulkan_setup_srgb_framebuffer(buffer, &dmabuf)) { + if (!vulkan_setup_one_pass_framebuffer(buffer, &dmabuf)) { goto error; } } else { From a91f96b391c9ea32ba0ba89875a360130eb830c9 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sat, 21 Jun 2025 17:39:42 +0200 Subject: [PATCH 122/311] render/vulkan: introduce wlr_vk_render_buffer_out Holds common state for final output buffer targets. --- include/render/vulkan.h | 16 +++++++++------- render/vulkan/pass.c | 16 ++++++++-------- render/vulkan/renderer.c | 24 ++++++++++++++---------- 3 files changed, 31 insertions(+), 25 deletions(-) diff --git a/include/render/vulkan.h b/include/render/vulkan.h index 52be0ba3d..ef5daceef 100644 --- a/include/render/vulkan.h +++ b/include/render/vulkan.h @@ -204,6 +204,13 @@ struct wlr_vk_render_format_setup { struct wl_list pipelines; // struct wlr_vk_pipeline.link }; +// Final output framebuffer and image view +struct wlr_vk_render_buffer_out { + VkImageView image_view; + VkFramebuffer framebuffer; + bool transitioned; +}; + // Renderer-internal represenation of an wlr_buffer imported for rendering. struct wlr_vk_render_buffer { struct wlr_buffer *wlr_buffer; @@ -219,22 +226,17 @@ struct wlr_vk_render_buffer { // This requires that the image support an _SRGB VkFormat, and does // not work with color transforms. struct { + struct wlr_vk_render_buffer_out out; struct wlr_vk_render_format_setup *render_setup; - VkImageView image_view; - VkFramebuffer framebuffer; - bool transitioned; } srgb; // Framebuffer, image view, and blending image to render indirectly // onto the buffer image. This works for general image types and permits // color transforms. struct { + struct wlr_vk_render_buffer_out out; struct wlr_vk_render_format_setup *render_setup; - VkImageView image_view; - VkFramebuffer framebuffer; - bool transitioned; - VkImage blend_image; VkImageView blend_image_view; VkDeviceMemory blend_memory; diff --git a/render/vulkan/pass.c b/render/vulkan/pass.c index 646e3cab6..c18761b9f 100644 --- a/render/vulkan/pass.c +++ b/render/vulkan/pass.c @@ -399,14 +399,14 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { // also add acquire/release barriers for the current render buffer VkImageLayout src_layout = VK_IMAGE_LAYOUT_GENERAL; if (pass->srgb_pathway) { - if (!render_buffer->srgb.transitioned) { + if (!render_buffer->srgb.out.transitioned) { src_layout = VK_IMAGE_LAYOUT_PREINITIALIZED; - render_buffer->srgb.transitioned = true; + render_buffer->srgb.out.transitioned = true; } } else { - if (!render_buffer->two_pass.transitioned) { + if (!render_buffer->two_pass.out.transitioned) { src_layout = VK_IMAGE_LAYOUT_PREINITIALIZED; - render_buffer->two_pass.transitioned = true; + render_buffer->two_pass.out.transitioned = true; } // The render pass changes the blend image layout from // color attachment to read only, so on each frame, before @@ -1193,7 +1193,7 @@ struct wlr_vk_render_pass *vulkan_begin_render_pass(struct wlr_vk_renderer *rend } bool using_srgb_pathway = inv_eotf == WLR_COLOR_TRANSFER_FUNCTION_SRGB && - buffer->srgb.framebuffer != VK_NULL_HANDLE; + buffer->srgb.out.framebuffer != VK_NULL_HANDLE; if (!using_srgb_pathway) { if (options != NULL && options->color_transform != NULL && @@ -1205,7 +1205,7 @@ struct wlr_vk_render_pass *vulkan_begin_render_pass(struct wlr_vk_renderer *rend } } - if (!buffer->two_pass.image_view) { + if (!buffer->two_pass.out.image_view) { struct wlr_dmabuf_attributes attribs; wlr_buffer_get_dmabuf(buffer->wlr_buffer, &attribs); if (!vulkan_setup_two_pass_framebuffer(buffer, &attribs)) { @@ -1273,10 +1273,10 @@ struct wlr_vk_render_pass *vulkan_begin_render_pass(struct wlr_vk_renderer *rend }; if (pass->srgb_pathway) { rp_info.renderPass = buffer->srgb.render_setup->render_pass; - rp_info.framebuffer = buffer->srgb.framebuffer; + rp_info.framebuffer = buffer->srgb.out.framebuffer; } else { rp_info.renderPass = buffer->two_pass.render_setup->render_pass; - rp_info.framebuffer = buffer->two_pass.framebuffer; + rp_info.framebuffer = buffer->two_pass.out.framebuffer; } vkCmdBeginRenderPass(cb->vk, &rp_info, VK_SUBPASS_CONTENTS_INLINE); diff --git a/render/vulkan/renderer.c b/render/vulkan/renderer.c index 4f69c0c94..23f75d93e 100644 --- a/render/vulkan/renderer.c +++ b/render/vulkan/renderer.c @@ -604,6 +604,12 @@ void vulkan_reset_command_buffer(struct wlr_vk_command_buffer *cb) { } } +static void finish_render_buffer_out(struct wlr_vk_render_buffer_out *out, + VkDevice dev) { + vkDestroyFramebuffer(dev, out->framebuffer, NULL); + vkDestroyImageView(dev, out->image_view, NULL); +} + static void destroy_render_buffer(struct wlr_vk_render_buffer *buffer) { wl_list_remove(&buffer->link); wlr_addon_finish(&buffer->addon); @@ -617,11 +623,9 @@ static void destroy_render_buffer(struct wlr_vk_render_buffer *buffer) { wlr_vk_error("vkQueueWaitIdle", res); } - vkDestroyFramebuffer(dev, buffer->srgb.framebuffer, NULL); - vkDestroyImageView(dev, buffer->srgb.image_view, NULL); + finish_render_buffer_out(&buffer->srgb.out, dev); - vkDestroyFramebuffer(dev, buffer->two_pass.framebuffer, NULL); - vkDestroyImageView(dev, buffer->two_pass.image_view, NULL); + finish_render_buffer_out(&buffer->two_pass.out, dev); vkDestroyImage(dev, buffer->two_pass.blend_image, NULL); vkFreeMemory(dev, buffer->two_pass.blend_memory, NULL); vkDestroyImageView(dev, buffer->two_pass.blend_image_view, NULL); @@ -676,7 +680,7 @@ bool vulkan_setup_two_pass_framebuffer(struct wlr_vk_render_buffer *buffer, }, }; - res = vkCreateImageView(dev, &view_info, NULL, &buffer->two_pass.image_view); + res = vkCreateImageView(dev, &view_info, NULL, &buffer->two_pass.out.image_view); if (res != VK_SUCCESS) { wlr_vk_error("vkCreateImageView failed", res); goto error; @@ -786,7 +790,7 @@ bool vulkan_setup_two_pass_framebuffer(struct wlr_vk_render_buffer *buffer, VkImageView attachments[] = { buffer->two_pass.blend_image_view, - buffer->two_pass.image_view, + buffer->two_pass.out.image_view, }; VkFramebufferCreateInfo fb_info = { .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, @@ -799,7 +803,7 @@ bool vulkan_setup_two_pass_framebuffer(struct wlr_vk_render_buffer *buffer, .renderPass = buffer->two_pass.render_setup->render_pass, }; - res = vkCreateFramebuffer(dev, &fb_info, NULL, &buffer->two_pass.framebuffer); + res = vkCreateFramebuffer(dev, &fb_info, NULL, &buffer->two_pass.out.framebuffer); if (res != VK_SUCCESS) { wlr_vk_error("vkCreateFramebuffer", res); goto error; @@ -844,7 +848,7 @@ static bool vulkan_setup_one_pass_framebuffer(struct wlr_vk_render_buffer *buffe }, }; - res = vkCreateImageView(dev, &view_info, NULL, &buffer->srgb.image_view); + res = vkCreateImageView(dev, &view_info, NULL, &buffer->srgb.out.image_view); if (res != VK_SUCCESS) { wlr_vk_error("vkCreateImageView failed", res); goto error; @@ -859,7 +863,7 @@ static bool vulkan_setup_one_pass_framebuffer(struct wlr_vk_render_buffer *buffe VkFramebufferCreateInfo fb_info = { .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, .attachmentCount = 1, - .pAttachments = &buffer->srgb.image_view, + .pAttachments = &buffer->srgb.out.image_view, .flags = 0u, .width = dmabuf->width, .height = dmabuf->height, @@ -867,7 +871,7 @@ static bool vulkan_setup_one_pass_framebuffer(struct wlr_vk_render_buffer *buffe .renderPass = buffer->srgb.render_setup->render_pass, }; - res = vkCreateFramebuffer(dev, &fb_info, NULL, &buffer->srgb.framebuffer); + res = vkCreateFramebuffer(dev, &fb_info, NULL, &buffer->srgb.out.framebuffer); if (res != VK_SUCCESS) { wlr_vk_error("vkCreateFramebuffer", res); goto error; From 35eba5f2fe7580031d386a0857414f47f10fa7a0 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sat, 21 Jun 2025 17:59:29 +0200 Subject: [PATCH 123/311] render/vulkan: add wlr_vk_render_pass.render_setup Simplifies the logic and prepares for a new render setup. --- include/render/vulkan.h | 1 + render/vulkan/pass.c | 17 +++++++---------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/include/render/vulkan.h b/include/render/vulkan.h index ef5daceef..20272882d 100644 --- a/include/render/vulkan.h +++ b/include/render/vulkan.h @@ -398,6 +398,7 @@ struct wlr_vk_render_pass { struct wlr_render_pass base; struct wlr_vk_renderer *renderer; struct wlr_vk_render_buffer *render_buffer; + struct wlr_vk_render_format_setup *render_setup; struct wlr_vk_command_buffer *command_buffer; struct rect_union updated_region; VkPipeline bound_pipeline; diff --git a/render/vulkan/pass.c b/render/vulkan/pass.c index c18761b9f..00f840175 100644 --- a/render/vulkan/pass.c +++ b/render/vulkan/pass.c @@ -678,11 +678,8 @@ static void render_pass_add_rect(struct wlr_render_pass *wlr_pass, wlr_matrix_project_box(matrix, &box, WL_OUTPUT_TRANSFORM_NORMAL, proj); wlr_matrix_multiply(matrix, pass->projection, matrix); - struct wlr_vk_render_format_setup *setup = pass->srgb_pathway ? - pass->render_buffer->srgb.render_setup : - pass->render_buffer->two_pass.render_setup; struct wlr_vk_pipeline *pipe = setup_get_or_create_pipeline( - setup, + pass->render_setup, &(struct wlr_vk_pipeline_key) { .source = WLR_VK_SHADER_SOURCE_SINGLE_COLOR, .layout = { .ycbcr_format = NULL }, @@ -805,11 +802,8 @@ static void render_pass_add_texture(struct wlr_render_pass *wlr_pass, break; } - struct wlr_vk_render_format_setup *setup = pass->srgb_pathway ? - pass->render_buffer->srgb.render_setup : - pass->render_buffer->two_pass.render_setup; struct wlr_vk_pipeline *pipe = setup_get_or_create_pipeline( - setup, + pass->render_setup, &(struct wlr_vk_pipeline_key) { .source = WLR_VK_SHADER_SOURCE_TEXTURE, .layout = { @@ -1215,6 +1209,9 @@ struct wlr_vk_render_pass *vulkan_begin_render_pass(struct wlr_vk_renderer *rend } } + struct wlr_vk_render_format_setup *render_setup = + using_srgb_pathway ? buffer->srgb.render_setup : buffer->two_pass.render_setup; + struct wlr_vk_render_pass *pass = calloc(1, sizeof(*pass)); if (pass == NULL) { return NULL; @@ -1270,12 +1267,11 @@ struct wlr_vk_render_pass *vulkan_begin_render_pass(struct wlr_vk_renderer *rend .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, .renderArea = rect, .clearValueCount = 0, + .renderPass = render_setup->render_pass, }; if (pass->srgb_pathway) { - rp_info.renderPass = buffer->srgb.render_setup->render_pass; rp_info.framebuffer = buffer->srgb.out.framebuffer; } else { - rp_info.renderPass = buffer->two_pass.render_setup->render_pass; rp_info.framebuffer = buffer->two_pass.out.framebuffer; } vkCmdBeginRenderPass(cb->vk, &rp_info, VK_SUBPASS_CONTENTS_INLINE); @@ -1292,6 +1288,7 @@ struct wlr_vk_render_pass *vulkan_begin_render_pass(struct wlr_vk_renderer *rend wlr_buffer_lock(buffer->wlr_buffer); pass->render_buffer = buffer; + pass->render_setup = render_setup; pass->command_buffer = cb; return pass; } From b2d09cdee9bcc512bda047c27942976561e44b5f Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sat, 21 Jun 2025 18:06:28 +0200 Subject: [PATCH 124/311] render/vulkan: add wlr_vk_render_pass.render_buffer_out Simplifies the logic and prepares for a new render setup. --- include/render/vulkan.h | 1 + render/vulkan/pass.c | 25 ++++++++++--------------- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/include/render/vulkan.h b/include/render/vulkan.h index 20272882d..5f84ca57d 100644 --- a/include/render/vulkan.h +++ b/include/render/vulkan.h @@ -398,6 +398,7 @@ struct wlr_vk_render_pass { struct wlr_render_pass base; struct wlr_vk_renderer *renderer; struct wlr_vk_render_buffer *render_buffer; + struct wlr_vk_render_buffer_out *render_buffer_out; struct wlr_vk_render_format_setup *render_setup; struct wlr_vk_command_buffer *command_buffer; struct rect_union updated_region; diff --git a/render/vulkan/pass.c b/render/vulkan/pass.c index 00f840175..c843b9b8f 100644 --- a/render/vulkan/pass.c +++ b/render/vulkan/pass.c @@ -398,16 +398,12 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { // also add acquire/release barriers for the current render buffer VkImageLayout src_layout = VK_IMAGE_LAYOUT_GENERAL; - if (pass->srgb_pathway) { - if (!render_buffer->srgb.out.transitioned) { - src_layout = VK_IMAGE_LAYOUT_PREINITIALIZED; - render_buffer->srgb.out.transitioned = true; - } - } else { - if (!render_buffer->two_pass.out.transitioned) { - src_layout = VK_IMAGE_LAYOUT_PREINITIALIZED; - render_buffer->two_pass.out.transitioned = true; - } + if (!pass->render_buffer_out->transitioned) { + src_layout = VK_IMAGE_LAYOUT_PREINITIALIZED; + pass->render_buffer_out->transitioned = true; + } + + if (!pass->srgb_pathway) { // The render pass changes the blend image layout from // color attachment to read only, so on each frame, before // the render pass starts, we change it back @@ -1211,6 +1207,8 @@ struct wlr_vk_render_pass *vulkan_begin_render_pass(struct wlr_vk_renderer *rend struct wlr_vk_render_format_setup *render_setup = using_srgb_pathway ? buffer->srgb.render_setup : buffer->two_pass.render_setup; + struct wlr_vk_render_buffer_out *buffer_out = + using_srgb_pathway ? &buffer->srgb.out : &buffer->two_pass.out; struct wlr_vk_render_pass *pass = calloc(1, sizeof(*pass)); if (pass == NULL) { @@ -1268,12 +1266,8 @@ struct wlr_vk_render_pass *vulkan_begin_render_pass(struct wlr_vk_renderer *rend .renderArea = rect, .clearValueCount = 0, .renderPass = render_setup->render_pass, + .framebuffer = buffer_out->framebuffer, }; - if (pass->srgb_pathway) { - rp_info.framebuffer = buffer->srgb.out.framebuffer; - } else { - rp_info.framebuffer = buffer->two_pass.out.framebuffer; - } vkCmdBeginRenderPass(cb->vk, &rp_info, VK_SUBPASS_CONTENTS_INLINE); vkCmdSetViewport(cb->vk, 0, 1, &(VkViewport){ @@ -1288,6 +1282,7 @@ struct wlr_vk_render_pass *vulkan_begin_render_pass(struct wlr_vk_renderer *rend wlr_buffer_lock(buffer->wlr_buffer); pass->render_buffer = buffer; + pass->render_buffer_out = buffer_out; pass->render_setup = render_setup; pass->command_buffer = cb; return pass; From 3e88a79e6f43dea779996673939561ad54cab4be Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sat, 21 Jun 2025 18:14:45 +0200 Subject: [PATCH 125/311] render/vulkan: replace wlr_vk_render_pass.srgb_pathway with two_pass The important bit here is whether this is using a single or two sub-passes. The flag isn't used for anything else. Preparation for an upcoming one-subpass codepath. --- include/render/vulkan.h | 2 +- render/vulkan/pass.c | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/include/render/vulkan.h b/include/render/vulkan.h index 5f84ca57d..1d6e9949e 100644 --- a/include/render/vulkan.h +++ b/include/render/vulkan.h @@ -405,7 +405,7 @@ struct wlr_vk_render_pass { VkPipeline bound_pipeline; float projection[9]; bool failed; - bool srgb_pathway; // if false, rendering via intermediate blending buffer + bool two_pass; // rendering via intermediate blending buffer struct wlr_color_transform *color_transform; bool has_primaries; diff --git a/render/vulkan/pass.c b/render/vulkan/pass.c index c843b9b8f..a950e2732 100644 --- a/render/vulkan/pass.c +++ b/render/vulkan/pass.c @@ -175,7 +175,7 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { assert(stage_cb != NULL); renderer->stage.cb = NULL; - if (!pass->srgb_pathway) { + if (pass->two_pass) { // Apply output shader to map blend image to actual output image vkCmdNextSubpass(render_cb->vk, VK_SUBPASS_CONTENTS_INLINE); @@ -403,7 +403,7 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { pass->render_buffer_out->transitioned = true; } - if (!pass->srgb_pathway) { + if (pass->two_pass) { // The render pass changes the blend image layout from // color attachment to read only, so on each frame, before // the render pass starts, we change it back @@ -614,7 +614,7 @@ error: static void render_pass_mark_box_updated(struct wlr_vk_render_pass *pass, const struct wlr_box *box) { - if (pass->srgb_pathway) { + if (!pass->two_pass) { return; } @@ -1217,7 +1217,7 @@ struct wlr_vk_render_pass *vulkan_begin_render_pass(struct wlr_vk_renderer *rend wlr_render_pass_init(&pass->base, &render_pass_impl); pass->renderer = renderer; - pass->srgb_pathway = using_srgb_pathway; + pass->two_pass = !using_srgb_pathway; if (options != NULL && options->color_transform != NULL) { pass->color_transform = wlr_color_transform_ref(options->color_transform); } From d1c88e94970eea1df013d78e47c0d9d904082795 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sat, 21 Jun 2025 18:22:43 +0200 Subject: [PATCH 126/311] render/vulkan: add linear single-subpass When the TF is set to EXT_LINEAR, we can write out color values straight up to a non-SRGB image view. --- include/render/vulkan.h | 9 +++++++++ render/vulkan/pass.c | 33 +++++++++++++++++++++++++++------ render/vulkan/renderer.c | 37 +++++++++++++++++++++++++------------ 3 files changed, 61 insertions(+), 18 deletions(-) diff --git a/include/render/vulkan.h b/include/render/vulkan.h index 1d6e9949e..33d158dee 100644 --- a/include/render/vulkan.h +++ b/include/render/vulkan.h @@ -222,6 +222,13 @@ struct wlr_vk_render_buffer { uint32_t mem_count; VkImage image; + // Framebuffer and image view for rendering directly onto the buffer image, + // without any color transform. + struct { + struct wlr_vk_render_buffer_out out; + struct wlr_vk_render_format_setup *render_setup; + } linear; + // Framebuffer and image view for rendering directly onto the buffer image. // This requires that the image support an _SRGB VkFormat, and does // not work with color transforms. @@ -246,6 +253,8 @@ struct wlr_vk_render_buffer { } two_pass; }; +bool vulkan_setup_one_pass_framebuffer(struct wlr_vk_render_buffer *buffer, + const struct wlr_dmabuf_attributes *dmabuf, bool srgb); bool vulkan_setup_two_pass_framebuffer(struct wlr_vk_render_buffer *buffer, const struct wlr_dmabuf_attributes *dmabuf); diff --git a/render/vulkan/pass.c b/render/vulkan/pass.c index a950e2732..4cbb3c26c 100644 --- a/render/vulkan/pass.c +++ b/render/vulkan/pass.c @@ -1182,10 +1182,21 @@ struct wlr_vk_render_pass *vulkan_begin_render_pass(struct wlr_vk_renderer *rend inv_eotf = WLR_COLOR_TRANSFER_FUNCTION_SRGB; } + bool using_linear_pathway = inv_eotf == WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR; bool using_srgb_pathway = inv_eotf == WLR_COLOR_TRANSFER_FUNCTION_SRGB && buffer->srgb.out.framebuffer != VK_NULL_HANDLE; + bool using_two_pass_pathway = !using_linear_pathway && !using_srgb_pathway; - if (!using_srgb_pathway) { + if (using_linear_pathway && !buffer->linear.out.image_view) { + struct wlr_dmabuf_attributes attribs; + wlr_buffer_get_dmabuf(buffer->wlr_buffer, &attribs); + if (!vulkan_setup_one_pass_framebuffer(buffer, &attribs, false)) { + wlr_log(WLR_ERROR, "Failed to set up blend image"); + return NULL; + } + } + + if (using_two_pass_pathway) { if (options != NULL && options->color_transform != NULL && !get_color_transform(options->color_transform, renderer)) { /* Try to create a new color transform */ @@ -1205,10 +1216,20 @@ struct wlr_vk_render_pass *vulkan_begin_render_pass(struct wlr_vk_renderer *rend } } - struct wlr_vk_render_format_setup *render_setup = - using_srgb_pathway ? buffer->srgb.render_setup : buffer->two_pass.render_setup; - struct wlr_vk_render_buffer_out *buffer_out = - using_srgb_pathway ? &buffer->srgb.out : &buffer->two_pass.out; + struct wlr_vk_render_format_setup *render_setup; + struct wlr_vk_render_buffer_out *buffer_out; + if (using_two_pass_pathway) { + render_setup = buffer->two_pass.render_setup; + buffer_out = &buffer->two_pass.out; + } else if (using_srgb_pathway) { + render_setup = buffer->srgb.render_setup; + buffer_out = &buffer->srgb.out; + } else if (using_linear_pathway) { + render_setup = buffer->linear.render_setup; + buffer_out = &buffer->linear.out; + } else { + abort(); // unreachable + } struct wlr_vk_render_pass *pass = calloc(1, sizeof(*pass)); if (pass == NULL) { @@ -1217,7 +1238,7 @@ struct wlr_vk_render_pass *vulkan_begin_render_pass(struct wlr_vk_renderer *rend wlr_render_pass_init(&pass->base, &render_pass_impl); pass->renderer = renderer; - pass->two_pass = !using_srgb_pathway; + pass->two_pass = using_two_pass_pathway; if (options != NULL && options->color_transform != NULL) { pass->color_transform = wlr_color_transform_ref(options->color_transform); } diff --git a/render/vulkan/renderer.c b/render/vulkan/renderer.c index 23f75d93e..a1a1bf6da 100644 --- a/render/vulkan/renderer.c +++ b/render/vulkan/renderer.c @@ -623,6 +623,7 @@ static void destroy_render_buffer(struct wlr_vk_render_buffer *buffer) { wlr_vk_error("vkQueueWaitIdle", res); } + finish_render_buffer_out(&buffer->linear.out, dev); finish_render_buffer_out(&buffer->srgb.out, dev); finish_render_buffer_out(&buffer->two_pass.out, dev); @@ -817,8 +818,8 @@ error: return false; } -static bool vulkan_setup_one_pass_framebuffer(struct wlr_vk_render_buffer *buffer, - const struct wlr_dmabuf_attributes *dmabuf) { +bool vulkan_setup_one_pass_framebuffer(struct wlr_vk_render_buffer *buffer, + const struct wlr_dmabuf_attributes *dmabuf, bool srgb) { struct wlr_vk_renderer *renderer = buffer->renderer; VkResult res; VkDevice dev = renderer->dev->dev; @@ -827,14 +828,18 @@ static bool vulkan_setup_one_pass_framebuffer(struct wlr_vk_render_buffer *buffe renderer->dev, dmabuf->format); assert(fmt); - assert(fmt->format.vk_srgb); + VkFormat vk_fmt = srgb ? fmt->format.vk_srgb : fmt->format.vk; + assert(vk_fmt != VK_FORMAT_UNDEFINED); + + struct wlr_vk_render_buffer_out *out = srgb ? &buffer->srgb.out : &buffer->linear.out; + // Set up the srgb framebuffer by default; two-pass framebuffer and // blending image will be set up later if necessary VkImageViewCreateInfo view_info = { .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .image = buffer->image, .viewType = VK_IMAGE_VIEW_TYPE_2D, - .format = fmt->format.vk_srgb, + .format = vk_fmt, .components.r = VK_COMPONENT_SWIZZLE_IDENTITY, .components.g = VK_COMPONENT_SWIZZLE_IDENTITY, .components.b = VK_COMPONENT_SWIZZLE_IDENTITY, @@ -848,35 +853,43 @@ static bool vulkan_setup_one_pass_framebuffer(struct wlr_vk_render_buffer *buffe }, }; - res = vkCreateImageView(dev, &view_info, NULL, &buffer->srgb.out.image_view); + res = vkCreateImageView(dev, &view_info, NULL, &out->image_view); if (res != VK_SUCCESS) { wlr_vk_error("vkCreateImageView failed", res); goto error; } - buffer->srgb.render_setup = find_or_create_render_setup( - renderer, &fmt->format, false); - if (!buffer->srgb.render_setup) { + struct wlr_vk_render_format_setup *render_setup = + find_or_create_render_setup(renderer, &fmt->format, false); + if (!render_setup) { goto error; } VkFramebufferCreateInfo fb_info = { .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, .attachmentCount = 1, - .pAttachments = &buffer->srgb.out.image_view, + .pAttachments = &out->image_view, .flags = 0u, .width = dmabuf->width, .height = dmabuf->height, .layers = 1u, - .renderPass = buffer->srgb.render_setup->render_pass, + .renderPass = render_setup->render_pass, }; - res = vkCreateFramebuffer(dev, &fb_info, NULL, &buffer->srgb.out.framebuffer); + res = vkCreateFramebuffer(dev, &fb_info, NULL, &out->framebuffer); if (res != VK_SUCCESS) { wlr_vk_error("vkCreateFramebuffer", res); goto error; } + + if (srgb) { + buffer->srgb.render_setup = render_setup; + } else { + buffer->linear.render_setup = render_setup; + } + return true; + error: // cleaning up everything is the caller's responsibility, // since it will need to do this anyway if framebuffer setup fails @@ -920,7 +933,7 @@ static struct wlr_vk_render_buffer *create_render_buffer( } if (using_mutable_srgb) { - if (!vulkan_setup_one_pass_framebuffer(buffer, &dmabuf)) { + if (!vulkan_setup_one_pass_framebuffer(buffer, &dmabuf, true)) { goto error; } } else { From aaf82ee332cf92c615a98ccd79fe3fe666a52b8f Mon Sep 17 00:00:00 2001 From: xurui Date: Thu, 7 Aug 2025 11:06:46 +0800 Subject: [PATCH 127/311] wlr_drag: drag motion signal also needs to be sent Signed-off-by: xurui --- types/data_device/wlr_drag.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/types/data_device/wlr_drag.c b/types/data_device/wlr_drag.c index b780eedac..8c17eeb68 100644 --- a/types/data_device/wlr_drag.c +++ b/types/data_device/wlr_drag.c @@ -308,6 +308,14 @@ static void drag_handle_touch_motion(struct wlr_seat_touch_grab *grab, wl_fixed_from_double(point->sx), wl_fixed_from_double(point->sy)); } + + struct wlr_drag_motion_event event = { + .drag = drag, + .time = time, + .sx = point->sx, + .sy = point->sy, + }; + wl_signal_emit_mutable(&drag->events.motion, &event); } } From 108d94f7980b0fa0d72aa1332b5169995e27f9d8 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sun, 21 Sep 2025 23:11:12 +0200 Subject: [PATCH 128/311] Add release script This is useful to speed up the release process and avoid making mistakes. --- release.sh | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100755 release.sh diff --git a/release.sh b/release.sh new file mode 100755 index 000000000..8531a23b4 --- /dev/null +++ b/release.sh @@ -0,0 +1,32 @@ +#!/bin/sh -eu + +prev=$(git describe --tags --abbrev=0) +next=$(meson rewrite kwargs info project / | jq -r '.kwargs["project#/"].version') + +case "$next" in +*-dev) + echo "This is a development version" + exit 1 + ;; +esac + +if [ "$prev" = "$next" ]; then + echo "Version not bumped in meson.build" + exit 1 +fi + +if ! git diff-index --quiet HEAD -- meson.build; then + echo "meson.build not committed" + exit 1 +fi + +shortlog="$(git shortlog --no-merges "$prev..")" +(echo "wlroots $next"; echo ""; echo "$shortlog") | git tag "$next" -ase -F - + +prefix=wlroots-$next +archive=$prefix.tar.gz +git archive --prefix="$prefix/" -o "$archive" "$next" +gpg --output "$archive".sig --detach-sig "$archive" + +git push --follow-tags +glab release create "$next" "$archive" "$archive.sig" --notes "" From 845a7a581d2ab3df42dbd67c63af02932f9da12c Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Tue, 29 Jul 2025 19:15:12 +0200 Subject: [PATCH 129/311] color_management_v1: drop duplicated enum converters --- types/wlr_color_management_v1.c | 32 ++++---------------------------- 1 file changed, 4 insertions(+), 28 deletions(-) diff --git a/types/wlr_color_management_v1.c b/types/wlr_color_management_v1.c index 55faaacaa..ea920a122 100644 --- a/types/wlr_color_management_v1.c +++ b/types/wlr_color_management_v1.c @@ -65,18 +65,6 @@ static void resource_handle_destroy(struct wl_client *client, struct wl_resource wl_resource_destroy(resource); } -static enum wlr_color_named_primaries named_primaries_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(); - } -} - static enum wp_color_manager_v1_primaries named_primaries_from_wlr( enum wlr_color_named_primaries primaries) { switch (primaries) { @@ -88,20 +76,6 @@ static enum wp_color_manager_v1_primaries named_primaries_from_wlr( abort(); } -static enum wlr_color_transfer_function transfer_function_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; - case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST2084_PQ: - return WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ; - case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR: - return WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR; - default: - abort(); - } -} - static enum wp_color_manager_v1_transfer_function transfer_function_from_wlr( enum wlr_color_transfer_function tf) { switch (tf) { @@ -156,10 +130,12 @@ static void image_desc_handle_get_information(struct wl_client *client, } struct wlr_color_primaries primaries; - wlr_color_primaries_from_named(&primaries, named_primaries_to_wlr(image_desc->data.primaries_named)); + wlr_color_primaries_from_named(&primaries, + wlr_color_manager_v1_primaries_to_wlr(image_desc->data.primaries_named)); struct wlr_color_luminances luminances; - wlr_color_transfer_function_get_default_luminance(transfer_function_to_wlr(image_desc->data.tf_named), &luminances); + wlr_color_transfer_function_get_default_luminance( + wlr_color_manager_v1_transfer_function_to_wlr(image_desc->data.tf_named), &luminances); wp_image_description_info_v1_send_primaries_named(resource, image_desc->data.primaries_named); wp_image_description_info_v1_send_primaries(resource, From 138210f01c4f3a7de04a6a119389e34728364231 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Tue, 29 Jul 2025 19:18:10 +0200 Subject: [PATCH 130/311] color_management_v1: make from_wlr enum converters public This can be useful for compositors to set surface feedback. --- include/wlr/types/wlr_color_management_v1.h | 12 +++++ types/wlr_color_management_v1.c | 52 ++++++++++----------- 2 files changed, 38 insertions(+), 26 deletions(-) diff --git a/include/wlr/types/wlr_color_management_v1.h b/include/wlr/types/wlr_color_management_v1.h index 4a50e94e3..b24b22ef9 100644 --- a/include/wlr/types/wlr_color_management_v1.h +++ b/include/wlr/types/wlr_color_management_v1.h @@ -96,6 +96,12 @@ 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); +/** + * Convert an enum wlr_color_transfer_function value into a protocol transfer function. + */ +enum wp_color_manager_v1_transfer_function +wlr_color_manager_v1_transfer_function_from_wlr(enum wlr_color_transfer_function tf); + /** * Convert a protocol named primaries to enum wlr_color_named_primaries. * Aborts if there is no matching wlroots entry. @@ -103,4 +109,10 @@ wlr_color_manager_v1_transfer_function_to_wlr(enum wp_color_manager_v1_transfer_ enum wlr_color_named_primaries wlr_color_manager_v1_primaries_to_wlr(enum wp_color_manager_v1_primaries primaries); +/** + * Convert an enum wlr_color_named_primaries value into protocol primaries. + */ +enum wp_color_manager_v1_primaries +wlr_color_manager_v1_primaries_from_wlr(enum wlr_color_named_primaries primaries); + #endif diff --git a/types/wlr_color_management_v1.c b/types/wlr_color_management_v1.c index ea920a122..dacbeed0c 100644 --- a/types/wlr_color_management_v1.c +++ b/types/wlr_color_management_v1.c @@ -65,30 +65,6 @@ static void resource_handle_destroy(struct wl_client *client, struct wl_resource wl_resource_destroy(resource); } -static enum wp_color_manager_v1_primaries named_primaries_from_wlr( - enum wlr_color_named_primaries primaries) { - switch (primaries) { - case WLR_COLOR_NAMED_PRIMARIES_SRGB: - return WP_COLOR_MANAGER_V1_PRIMARIES_SRGB; - case WLR_COLOR_NAMED_PRIMARIES_BT2020: - return WP_COLOR_MANAGER_V1_PRIMARIES_BT2020; - } - abort(); -} - -static enum wp_color_manager_v1_transfer_function transfer_function_from_wlr( - enum wlr_color_transfer_function tf) { - switch (tf) { - case WLR_COLOR_TRANSFER_FUNCTION_SRGB: - return WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_SRGB; - case WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ: - return WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST2084_PQ; - case WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR: - return WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR; - } - abort(); -} - static int32_t encode_cie1931_coord(float value) { return round(value * 1000 * 1000); } @@ -241,8 +217,8 @@ static void cm_output_handle_get_image_description(struct wl_client *client, }; const struct wlr_output_image_description *image_desc = cm_output->output->image_description; if (image_desc != NULL) { - data.tf_named = transfer_function_from_wlr(image_desc->transfer_function); - data.primaries_named = named_primaries_from_wlr(image_desc->primaries); + data.tf_named = wlr_color_manager_v1_transfer_function_from_wlr(image_desc->transfer_function); + data.primaries_named = wlr_color_manager_v1_primaries_from_wlr(image_desc->primaries); } image_desc_create_ready(cm_output->manager, cm_output_resource, id, &data, true); } @@ -1019,6 +995,19 @@ wlr_color_manager_v1_transfer_function_to_wlr(enum wp_color_manager_v1_transfer_ } } +enum wp_color_manager_v1_transfer_function +wlr_color_manager_v1_transfer_function_from_wlr(enum wlr_color_transfer_function tf) { + switch (tf) { + case WLR_COLOR_TRANSFER_FUNCTION_SRGB: + return WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_SRGB; + case WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ: + return WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST2084_PQ; + case WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR: + return WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR; + } + abort(); +} + enum wlr_color_named_primaries wlr_color_manager_v1_primaries_to_wlr(enum wp_color_manager_v1_primaries primaries) { switch (primaries) { @@ -1030,3 +1019,14 @@ wlr_color_manager_v1_primaries_to_wlr(enum wp_color_manager_v1_primaries primari abort(); } } + +enum wp_color_manager_v1_primaries +wlr_color_manager_v1_primaries_from_wlr(enum wlr_color_named_primaries primaries) { + switch (primaries) { + case WLR_COLOR_NAMED_PRIMARIES_SRGB: + return WP_COLOR_MANAGER_V1_PRIMARIES_SRGB; + case WLR_COLOR_NAMED_PRIMARIES_BT2020: + return WP_COLOR_MANAGER_V1_PRIMARIES_BT2020; + } + abort(); +} From 26c1476827dfd175fcf0e2b6e027292c78ce3f55 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Tue, 29 Jul 2025 19:29:24 +0200 Subject: [PATCH 131/311] color_management_v1: add destroy event to manager --- include/wlr/types/wlr_color_management_v1.h | 4 ++++ types/wlr_color_management_v1.c | 3 +++ 2 files changed, 7 insertions(+) diff --git a/include/wlr/types/wlr_color_management_v1.h b/include/wlr/types/wlr_color_management_v1.h index b24b22ef9..e6cb7dfbb 100644 --- a/include/wlr/types/wlr_color_management_v1.h +++ b/include/wlr/types/wlr_color_management_v1.h @@ -58,6 +58,10 @@ struct wlr_color_manager_v1_options { struct wlr_color_manager_v1 { struct wl_global *global; + struct { + struct wl_signal destroy; + } events; + struct { struct wlr_color_manager_v1_features features; diff --git a/types/wlr_color_management_v1.c b/types/wlr_color_management_v1.c index dacbeed0c..4524015fd 100644 --- a/types/wlr_color_management_v1.c +++ b/types/wlr_color_management_v1.c @@ -887,6 +887,8 @@ static void manager_bind(struct wl_client *client, void *data, static void manager_handle_display_destroy(struct wl_listener *listener, void *data) { struct wlr_color_manager_v1 *manager = wl_container_of(listener, manager, display_destroy); + wl_signal_emit_mutable(&manager->events.destroy, NULL); + assert(wl_list_empty(&manager->events.destroy.listener_list)); wl_list_remove(&manager->display_destroy.link); wl_global_destroy(manager->global); free(manager->render_intents); @@ -934,6 +936,7 @@ struct wlr_color_manager_v1 *wlr_color_manager_v1_create(struct wl_display *disp manager->transfer_functions_len = options->transfer_functions_len; manager->primaries_len = options->primaries_len; + wl_signal_init(&manager->events.destroy); wl_list_init(&manager->outputs); wl_list_init(&manager->surface_feedbacks); From 7cb3393e75667ca349287ad70aef3f7e2d008ec6 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Tue, 29 Jul 2025 19:29:45 +0200 Subject: [PATCH 132/311] scene: send color_management_v1 surface feedback Closes: https://gitlab.freedesktop.org/wlroots/wlroots/-/issues/3999 --- include/wlr/types/wlr_scene.h | 10 ++++++ types/scene/surface.c | 58 +++++++++++++++++++++++++++++++++++ types/scene/wlr_scene.c | 16 ++++++++++ 3 files changed, 84 insertions(+) diff --git a/include/wlr/types/wlr_scene.h b/include/wlr/types/wlr_scene.h index 0f4aa4c27..58794e8db 100644 --- a/include/wlr/types/wlr_scene.h +++ b/include/wlr/types/wlr_scene.h @@ -43,6 +43,7 @@ struct wlr_scene_output_layout; struct wlr_presentation; struct wlr_linux_dmabuf_v1; struct wlr_gamma_control_manager_v1; +struct wlr_color_manager_v1; struct wlr_output_state; typedef bool (*wlr_scene_buffer_point_accepts_input_func_t)( @@ -102,11 +103,13 @@ struct wlr_scene { // May be NULL struct wlr_linux_dmabuf_v1 *linux_dmabuf_v1; struct wlr_gamma_control_manager_v1 *gamma_control_manager_v1; + struct wlr_color_manager_v1 *color_manager_v1; struct { struct wl_listener linux_dmabuf_v1_destroy; struct wl_listener gamma_control_manager_v1_destroy; struct wl_listener gamma_control_manager_v1_set_gamma; + struct wl_listener color_manager_v1_destroy; enum wlr_scene_debug_damage_option debug_damage_option; bool direct_scanout; @@ -366,6 +369,13 @@ void wlr_scene_set_linux_dmabuf_v1(struct wlr_scene *scene, void wlr_scene_set_gamma_control_manager_v1(struct wlr_scene *scene, struct wlr_gamma_control_manager_v1 *gamma_control); +/** + * Handles color_management_v1 feedback for all surfaces in the scene. + * + * Asserts that a struct wlr_color_manager_v1 hasn't already been set for the scene. + */ +void wlr_scene_set_color_manager_v1(struct wlr_scene *scene, struct wlr_color_manager_v1 *manager); + /** * Add a node displaying nothing but its children. */ diff --git a/types/scene/surface.c b/types/scene/surface.c index 135ded6fd..c798abfb3 100644 --- a/types/scene/surface.c +++ b/types/scene/surface.c @@ -35,16 +35,74 @@ static struct wlr_output *get_surface_frame_pacing_output(struct wlr_surface *su return frame_pacing_output; } +static bool get_tf_preference(enum wlr_color_transfer_function tf) { + switch (tf) { + case WLR_COLOR_TRANSFER_FUNCTION_SRGB: + return 0; + case WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ: + return 1; + case WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR: + return -1; + } + abort(); // unreachable +} + +static bool get_primaries_preference(enum wlr_color_named_primaries primaries) { + switch (primaries) { + case WLR_COLOR_NAMED_PRIMARIES_SRGB: + return 0; + case WLR_COLOR_NAMED_PRIMARIES_BT2020: + return 1; + } + abort(); // unreachable +} + +static void get_surface_preferred_image_description(struct wlr_surface *surface, + struct wlr_image_description_v1_data *out) { + struct wlr_output_image_description preferred = { + .transfer_function = WLR_COLOR_TRANSFER_FUNCTION_SRGB, + .primaries = WLR_COLOR_NAMED_PRIMARIES_SRGB, + }; + + struct wlr_surface_output *surface_output; + wl_list_for_each(surface_output, &surface->current_outputs, link) { + const struct wlr_output_image_description *img_desc = + surface_output->output->image_description; + if (img_desc == NULL) { + continue; + } + if (get_tf_preference(preferred.transfer_function) < get_tf_preference(img_desc->transfer_function)) { + preferred.transfer_function = img_desc->transfer_function; + } + if (get_primaries_preference(preferred.primaries) < get_primaries_preference(img_desc->primaries)) { + preferred.primaries = img_desc->primaries; + } + } + + *out = (struct wlr_image_description_v1_data){ + .tf_named = wlr_color_manager_v1_transfer_function_from_wlr(preferred.transfer_function), + .primaries_named = wlr_color_manager_v1_primaries_from_wlr(preferred.primaries), + }; +} + static void handle_scene_buffer_outputs_update( struct wl_listener *listener, void *data) { struct wlr_scene_surface *surface = wl_container_of(listener, surface, outputs_update); + struct wlr_scene *scene = scene_node_get_root(&surface->buffer->node); surface->frame_pacing_output = get_surface_frame_pacing_output(surface->surface); double scale = get_surface_preferred_buffer_scale(surface->surface); wlr_fractional_scale_v1_notify_scale(surface->surface, scale); wlr_surface_set_preferred_buffer_scale(surface->surface, ceil(scale)); + + if (scene->color_manager_v1 != NULL) { + struct wlr_image_description_v1_data img_desc = {0}; + get_surface_preferred_image_description(surface->surface, &img_desc); + wlr_color_manager_v1_set_surface_preferred_image_description(scene->color_manager_v1, + surface->surface, &img_desc); + } } static void handle_scene_buffer_output_enter( diff --git a/types/scene/wlr_scene.c b/types/scene/wlr_scene.c index d2327eda2..ac3ea3ecf 100644 --- a/types/scene/wlr_scene.c +++ b/types/scene/wlr_scene.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -1562,6 +1563,21 @@ void wlr_scene_set_gamma_control_manager_v1(struct wlr_scene *scene, wl_signal_add(&gamma_control->events.set_gamma, &scene->gamma_control_manager_v1_set_gamma); } +static void scene_handle_color_manager_v1_destroy(struct wl_listener *listener, void *data) { + struct wlr_scene *scene = wl_container_of(listener, scene, color_manager_v1_destroy); + wl_list_remove(&scene->color_manager_v1_destroy.link); + wl_list_init(&scene->color_manager_v1_destroy.link); + scene->color_manager_v1 = NULL; +} + +void wlr_scene_set_color_manager_v1(struct wlr_scene *scene, struct wlr_color_manager_v1 *manager) { + assert(scene->color_manager_v1 == NULL); + scene->color_manager_v1 = manager; + + scene->color_manager_v1_destroy.notify = scene_handle_color_manager_v1_destroy; + wl_signal_add(&manager->events.destroy, &scene->color_manager_v1_destroy); +} + static void scene_output_handle_destroy(struct wlr_addon *addon) { struct wlr_scene_output *scene_output = wl_container_of(addon, scene_output, addon); From 60d72724cd740a2f9bebf56deeb645d1d4a23a30 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Tue, 30 Sep 2025 09:23:24 +0200 Subject: [PATCH 133/311] render/color: fix bounds check in lut_1d_get() i == len is out-of-bounds. Fixes: 74217a4d9341 ("render/color: introduce COLOR_TRANSFORM_LUT_3X1D") --- render/color.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/render/color.c b/render/color.c index ca8236515..ae9309dd1 100644 --- a/render/color.c +++ b/render/color.c @@ -109,7 +109,7 @@ struct wlr_color_transform_lut_3x1d *color_transform_lut_3x1d_from_base( } static float lut_1d_get(const uint16_t *lut, size_t len, size_t i) { - if (i > len) { + if (i >= len) { i = len - 1; } return (float) lut[i] / UINT16_MAX; From 3f0d338643e97de500298bbf378e340705690cce Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Mon, 26 Jun 2023 19:40:52 +0200 Subject: [PATCH 134/311] backend/wayland: log when getting disconnected from remote display It can be a bit confusing to understand why a compositor is shutting down on its own. Log a message when we get disconnected from the parent compositor to explain the cause. --- backend/wayland/backend.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/wayland/backend.c b/backend/wayland/backend.c index ff95a7b4b..fbae3a3c2 100644 --- a/backend/wayland/backend.c +++ b/backend/wayland/backend.c @@ -58,6 +58,8 @@ static int dispatch_events(int fd, uint32_t mask, void *data) { if ((mask & WL_EVENT_HANGUP) || (mask & WL_EVENT_ERROR)) { if (mask & WL_EVENT_ERROR) { wlr_log(WLR_ERROR, "Failed to read from remote Wayland display"); + } else { + wlr_log(WLR_DEBUG, "Disconnected from remote Wayland display"); } wlr_backend_destroy(&wl->backend); return 0; From d039ad8da3a92f41bbed409e685535bcceb39b33 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Mon, 26 Jun 2023 19:54:08 +0200 Subject: [PATCH 135/311] backend/wayland: continue reading on hangup If we stop immediately, we won't see any wl_display.error events. Make sure we've read everything before handling hangup. --- backend/wayland/backend.c | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/backend/wayland/backend.c b/backend/wayland/backend.c index fbae3a3c2..14a783b67 100644 --- a/backend/wayland/backend.c +++ b/backend/wayland/backend.c @@ -55,16 +55,6 @@ struct wlr_wl_backend *get_wl_backend_from_backend(struct wlr_backend *wlr_backe static int dispatch_events(int fd, uint32_t mask, void *data) { struct wlr_wl_backend *wl = data; - if ((mask & WL_EVENT_HANGUP) || (mask & WL_EVENT_ERROR)) { - if (mask & WL_EVENT_ERROR) { - wlr_log(WLR_ERROR, "Failed to read from remote Wayland display"); - } else { - wlr_log(WLR_DEBUG, "Disconnected from remote Wayland display"); - } - wlr_backend_destroy(&wl->backend); - return 0; - } - int count = 0; if (mask & WL_EVENT_READABLE) { count = wl_display_dispatch(wl->remote_display); @@ -77,6 +67,18 @@ static int dispatch_events(int fd, uint32_t mask, void *data) { wl_display_flush(wl->remote_display); } + // Make sure we've consumed all data before disconnecting due to hangup, + // so that we process any wl_display.error events + if (!(mask & WL_EVENT_READABLE) && (mask & (WL_EVENT_HANGUP | WL_EVENT_ERROR))) { + if (mask & WL_EVENT_ERROR) { + wlr_log(WLR_ERROR, "Failed to read from remote Wayland display"); + } else { + wlr_log(WLR_DEBUG, "Disconnected from remote Wayland display"); + } + wlr_backend_destroy(&wl->backend); + return 0; + } + if (count < 0) { wlr_log(WLR_ERROR, "Failed to dispatch remote Wayland display"); wlr_backend_destroy(&wl->backend); From 2ec4012559d69c6acff0b557e1b533ddf5ced918 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Wed, 1 Oct 2025 15:14:46 +0200 Subject: [PATCH 136/311] backend/drm: avoid error message when EDID is missing We'd attempt to parse an EDID even when the connector has no EDID, printing "Failed to parse EDID" in logs. Instead, don't attempt to parse the EDID and print a more appropriate log message. --- backend/drm/drm.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/backend/drm/drm.c b/backend/drm/drm.c index 15cb181cf..86b52c684 100644 --- a/backend/drm/drm.c +++ b/backend/drm/drm.c @@ -1717,7 +1717,11 @@ static bool connect_drm_connector(struct wlr_drm_connector *wlr_conn, size_t edid_len = 0; uint8_t *edid = get_drm_prop_blob(drm->fd, wlr_conn->id, wlr_conn->props.edid, &edid_len); - parse_edid(wlr_conn, edid_len, edid); + if (edid_len > 0) { + parse_edid(wlr_conn, edid_len, edid); + } else { + wlr_log(WLR_DEBUG, "Connector has no EDID"); + } free(edid); char *subconnector = NULL; From 406aa5f7f5649a9774fd89880037be3a731e96fa Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Wed, 1 Oct 2025 16:58:49 +0200 Subject: [PATCH 137/311] backend/session: fix crash on udev device remove event libwayland adds phantom listeners here: https://gitlab.freedesktop.org/wayland/wayland/-/blob/d81525a235e48cc5de3e4005a16ddb1fbdfd9d7c/src/wayland-server.c#L2378 Closes: https://gitlab.freedesktop.org/wlroots/wlroots/-/issues/3982 --- backend/session/session.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/backend/session/session.c b/backend/session/session.c index dcf07c708..48f4ab187 100644 --- a/backend/session/session.c +++ b/backend/session/session.c @@ -367,7 +367,10 @@ void wlr_session_close_file(struct wlr_session *session, } assert(wl_list_empty(&dev->events.change.listener_list)); - assert(wl_list_empty(&dev->events.remove.listener_list)); + // TODO: assert that the "remove" listener list is empty as well. Listeners + // will typically call wlr_session_close_file() in response, and + // wl_signal_emit_mutable() installs two phantom listeners, so we'd count + // these two. close(dev->fd); wl_list_remove(&dev->link); From dde07b68404a54ba0134baae1c1b429fdce6a707 Mon Sep 17 00:00:00 2001 From: llyyr Date: Thu, 2 Oct 2025 11:29:53 +0530 Subject: [PATCH 138/311] wlr_scene: fix tf/prim comparison for scanout attempt We were incorrectly doing comparison with `!= 0` to detect non-sRGB tf/primaries. Since these enums are bit flags, the default sRGB values are 1, not 0, so sRGB buffers were incorrectly rejected. Fixes: bf40f396bfd0 ("scene: grab image description from output state") --- types/scene/wlr_scene.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/types/scene/wlr_scene.c b/types/scene/wlr_scene.c index ac3ea3ecf..c9454abcb 100644 --- a/types/scene/wlr_scene.c +++ b/types/scene/wlr_scene.c @@ -1982,7 +1982,8 @@ static enum scene_direct_scanout_result scene_entry_try_direct_scanout( } const struct wlr_output_image_description *img_desc = output_pending_image_description(scene_output->output, state); - if (buffer->transfer_function != 0 || buffer->primaries != 0) { + if (buffer->transfer_function != WLR_COLOR_TRANSFER_FUNCTION_SRGB || + buffer->primaries != WLR_COLOR_NAMED_PRIMARIES_SRGB) { if (img_desc == NULL || img_desc->transfer_function != buffer->transfer_function || img_desc->primaries != buffer->primaries) { return false; From 22528542970687720556035790212df8d9bb30bb Mon Sep 17 00:00:00 2001 From: llyyr Date: Thu, 2 Oct 2025 13:51:41 +0530 Subject: [PATCH 139/311] wlr_scene: return scene_direct_scanout_result instead of bool --- types/scene/wlr_scene.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/types/scene/wlr_scene.c b/types/scene/wlr_scene.c index c9454abcb..537cfe910 100644 --- a/types/scene/wlr_scene.c +++ b/types/scene/wlr_scene.c @@ -1986,17 +1986,17 @@ static enum scene_direct_scanout_result scene_entry_try_direct_scanout( buffer->primaries != WLR_COLOR_NAMED_PRIMARIES_SRGB) { if (img_desc == NULL || img_desc->transfer_function != buffer->transfer_function || img_desc->primaries != buffer->primaries) { - return false; + return SCANOUT_INELIGIBLE; } } else if (img_desc != NULL) { - return false; + return SCANOUT_INELIGIBLE; } if (buffer->transfer_function != 0 && buffer->transfer_function != WLR_COLOR_TRANSFER_FUNCTION_SRGB) { - return false; + return SCANOUT_INELIGIBLE; } if (buffer->primaries != 0 && buffer->primaries != WLR_COLOR_NAMED_PRIMARIES_SRGB) { - return false; + return SCANOUT_INELIGIBLE; } // We want to ensure optimal buffer selection, but as direct-scanout can be enabled and disabled From 6978509f64a729984998c92301fdf83d6da7e4e1 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Fri, 3 Oct 2025 20:42:21 +0200 Subject: [PATCH 140/311] Revert "wlr_scene: fix tf/prim comparison for scanout attempt" This reverts commit dde07b68404a54ba0134baae1c1b429fdce6a707. This is incorrect as discussed here: https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/5163#note_3118744 --- types/scene/wlr_scene.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/types/scene/wlr_scene.c b/types/scene/wlr_scene.c index 537cfe910..9efbe58ad 100644 --- a/types/scene/wlr_scene.c +++ b/types/scene/wlr_scene.c @@ -1982,8 +1982,7 @@ static enum scene_direct_scanout_result scene_entry_try_direct_scanout( } const struct wlr_output_image_description *img_desc = output_pending_image_description(scene_output->output, state); - if (buffer->transfer_function != WLR_COLOR_TRANSFER_FUNCTION_SRGB || - buffer->primaries != WLR_COLOR_NAMED_PRIMARIES_SRGB) { + if (buffer->transfer_function != 0 || buffer->primaries != 0) { if (img_desc == NULL || img_desc->transfer_function != buffer->transfer_function || img_desc->primaries != buffer->primaries) { return SCANOUT_INELIGIBLE; From c2d9ae21425ae968f4491a73ea024df1338c52cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Poisot?= Date: Sun, 17 Aug 2025 15:51:26 +0000 Subject: [PATCH 141/311] render: introduce Gamma 2.2 color transform --- backend/drm/atomic.c | 2 ++ include/render/vulkan.h | 3 +++ include/wlr/render/color.h | 1 + render/vulkan/pass.c | 9 ++++++++- render/vulkan/renderer.c | 6 ++++++ render/vulkan/shaders/output.frag | 3 +++ render/vulkan/shaders/texture.frag | 3 +++ types/scene/surface.c | 1 + types/wlr_color_management_v1.c | 4 ++++ 9 files changed, 31 insertions(+), 1 deletion(-) diff --git a/backend/drm/atomic.c b/backend/drm/atomic.c index 33ce6f42d..7b4b4636a 100644 --- a/backend/drm/atomic.c +++ b/backend/drm/atomic.c @@ -186,6 +186,8 @@ static uint8_t convert_cta861_eotf(enum wlr_color_transfer_function tf) { return 2; case WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR: abort(); // unsupported + case WLR_COLOR_TRANSFER_FUNCTION_GAMMA22: + abort(); // unsupported } abort(); // unreachable } diff --git a/include/render/vulkan.h b/include/render/vulkan.h index 33d158dee..498661070 100644 --- a/include/render/vulkan.h +++ b/include/render/vulkan.h @@ -153,6 +153,7 @@ enum wlr_vk_texture_transform { WLR_VK_TEXTURE_TRANSFORM_IDENTITY = 0, WLR_VK_TEXTURE_TRANSFORM_SRGB = 1, WLR_VK_TEXTURE_TRANSFORM_ST2084_PQ = 2, + WLR_VK_TEXTURE_TRANSFORM_GAMMA22 = 3, }; enum wlr_vk_shader_source { @@ -167,6 +168,7 @@ enum wlr_vk_output_transform { WLR_VK_OUTPUT_TRANSFORM_INVERSE_SRGB = 1, WLR_VK_OUTPUT_TRANSFORM_INVERSE_ST2084_PQ = 2, WLR_VK_OUTPUT_TRANSFORM_LUT3D = 3, + WLR_VK_OUTPUT_TRANSFORM_INVERSE_GAMMA22 = 4, }; struct wlr_vk_pipeline_key { @@ -199,6 +201,7 @@ struct wlr_vk_render_format_setup { VkPipeline output_pipe_srgb; VkPipeline output_pipe_pq; VkPipeline output_pipe_lut3d; + VkPipeline output_pipe_gamma22; struct wlr_vk_renderer *renderer; struct wl_list pipelines; // struct wlr_vk_pipeline.link diff --git a/include/wlr/render/color.h b/include/wlr/render/color.h index e2397b75a..7d5b26857 100644 --- a/include/wlr/render/color.h +++ b/include/wlr/render/color.h @@ -28,6 +28,7 @@ enum wlr_color_transfer_function { WLR_COLOR_TRANSFER_FUNCTION_SRGB = 1 << 0, WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ = 1 << 1, WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR = 1 << 2, + WLR_COLOR_TRANSFER_FUNCTION_GAMMA22 = 1 << 3, }; /** diff --git a/render/vulkan/pass.c b/render/vulkan/pass.c index 4cbb3c26c..5555f1197 100644 --- a/render/vulkan/pass.c +++ b/render/vulkan/pass.c @@ -246,6 +246,9 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { case WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ: pipeline = render_buffer->two_pass.render_setup->output_pipe_pq; break; + case WLR_COLOR_TRANSFER_FUNCTION_GAMMA22: + pipeline = render_buffer->two_pass.render_setup->output_pipe_gamma22; + break; } struct wlr_color_luminances srgb_lum, dst_lum; @@ -796,6 +799,9 @@ static void render_pass_add_texture(struct wlr_render_pass *wlr_pass, case WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ: tex_transform = WLR_VK_TEXTURE_TRANSFORM_ST2084_PQ; break; + case WLR_COLOR_TRANSFER_FUNCTION_GAMMA22: + tex_transform = WLR_VK_TEXTURE_TRANSFORM_GAMMA22; + break; } struct wlr_vk_pipeline *pipe = setup_get_or_create_pipeline( @@ -840,7 +846,8 @@ static void render_pass_add_texture(struct wlr_render_pass *wlr_pass, } float luminance_multiplier = 1; - if (tf != WLR_COLOR_TRANSFER_FUNCTION_SRGB) { + if (tf != WLR_COLOR_TRANSFER_FUNCTION_SRGB + && tf != WLR_COLOR_TRANSFER_FUNCTION_GAMMA22) { struct wlr_color_luminances src_lum, srgb_lum; wlr_color_transfer_function_get_default_luminance(tf, &src_lum); wlr_color_transfer_function_get_default_luminance( diff --git a/render/vulkan/renderer.c b/render/vulkan/renderer.c index a1a1bf6da..ba14bc2ba 100644 --- a/render/vulkan/renderer.c +++ b/render/vulkan/renderer.c @@ -175,6 +175,7 @@ static void destroy_render_format_setup(struct wlr_vk_renderer *renderer, vkDestroyPipeline(dev, setup->output_pipe_srgb, NULL); vkDestroyPipeline(dev, setup->output_pipe_pq, NULL); vkDestroyPipeline(dev, setup->output_pipe_lut3d, NULL); + vkDestroyPipeline(dev, setup->output_pipe_gamma22, NULL); struct wlr_vk_pipeline *pipeline, *tmp_pipeline; wl_list_for_each_safe(pipeline, tmp_pipeline, &setup->pipelines, link) { @@ -2345,6 +2346,11 @@ static struct wlr_vk_render_format_setup *find_or_create_render_setup( &setup->output_pipe_pq, WLR_VK_OUTPUT_TRANSFORM_INVERSE_ST2084_PQ)) { goto error; } + if (!init_blend_to_output_pipeline( + renderer, setup->render_pass, renderer->output_pipe_layout, + &setup->output_pipe_gamma22, WLR_VK_OUTPUT_TRANSFORM_INVERSE_GAMMA22)) { + goto error; + } } else { assert(format->vk_srgb); VkAttachmentDescription attachment = { diff --git a/render/vulkan/shaders/output.frag b/render/vulkan/shaders/output.frag index 3d5ac4089..9785fd225 100644 --- a/render/vulkan/shaders/output.frag +++ b/render/vulkan/shaders/output.frag @@ -22,6 +22,7 @@ layout (constant_id = 0) const int OUTPUT_TRANSFORM = 0; #define OUTPUT_TRANSFORM_INVERSE_SRGB 1 #define OUTPUT_TRANSFORM_INVERSE_ST2084_PQ 2 #define OUTPUT_TRANSFORM_LUT_3D 3 +#define OUTPUT_TRANSFORM_INVERSE_GAMMA22 4 float linear_channel_to_srgb(float x) { return max(min(x * 12.92, 0.04045), 1.055 * pow(x, 1. / 2.4) - 0.055); @@ -71,6 +72,8 @@ void main() { } else if (OUTPUT_TRANSFORM == OUTPUT_TRANSFORM_INVERSE_SRGB) { // Produce sRGB encoded values rgb = linear_color_to_srgb(rgb); + } else if (OUTPUT_TRANSFORM == OUTPUT_TRANSFORM_INVERSE_GAMMA22) { + rgb = pow(rgb, vec3(1. / 2.2)); } // Back to pre-multiplied alpha diff --git a/render/vulkan/shaders/texture.frag b/render/vulkan/shaders/texture.frag index 2a7e2c517..bb73681f5 100644 --- a/render/vulkan/shaders/texture.frag +++ b/render/vulkan/shaders/texture.frag @@ -18,6 +18,7 @@ layout (constant_id = 0) const int TEXTURE_TRANSFORM = 0; #define TEXTURE_TRANSFORM_IDENTITY 0 #define TEXTURE_TRANSFORM_SRGB 1 #define TEXTURE_TRANSFORM_ST2084_PQ 2 +#define TEXTURE_TRANSFORM_GAMMA22 3 float srgb_channel_to_linear(float x) { return mix(x / 12.92, @@ -60,6 +61,8 @@ void main() { rgb = srgb_color_to_linear(rgb); } else if (TEXTURE_TRANSFORM == TEXTURE_TRANSFORM_ST2084_PQ) { rgb = pq_color_to_linear(rgb); + } else if (TEXTURE_TRANSFORM == TEXTURE_TRANSFORM_GAMMA22) { + rgb = pow(rgb, vec3(2.2)); } rgb *= data.luminance_multiplier; diff --git a/types/scene/surface.c b/types/scene/surface.c index c798abfb3..aebc00ea2 100644 --- a/types/scene/surface.c +++ b/types/scene/surface.c @@ -42,6 +42,7 @@ static bool get_tf_preference(enum wlr_color_transfer_function tf) { case WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ: return 1; case WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR: + case WLR_COLOR_TRANSFER_FUNCTION_GAMMA22: return -1; } abort(); // unreachable diff --git a/types/wlr_color_management_v1.c b/types/wlr_color_management_v1.c index 4524015fd..54c0a44af 100644 --- a/types/wlr_color_management_v1.c +++ b/types/wlr_color_management_v1.c @@ -993,6 +993,8 @@ wlr_color_manager_v1_transfer_function_to_wlr(enum wp_color_manager_v1_transfer_ return WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ; case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR: return WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR; + case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA22: + return WLR_COLOR_TRANSFER_FUNCTION_GAMMA22; default: abort(); } @@ -1007,6 +1009,8 @@ wlr_color_manager_v1_transfer_function_from_wlr(enum wlr_color_transfer_function return WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST2084_PQ; case WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR: return WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR; + case WLR_COLOR_TRANSFER_FUNCTION_GAMMA22: + return WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA22; } abort(); } From d8fb7adcf041af7e958804b77c9a6669fbff4efd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Poisot?= Date: Sun, 17 Aug 2025 15:57:16 +0000 Subject: [PATCH 142/311] scene, render: use Gamma 2.2 TF as default --- include/wlr/render/pass.h | 5 +++-- render/vulkan/pass.c | 11 ++++------- types/scene/surface.c | 8 ++++---- types/scene/wlr_scene.c | 2 +- types/wlr_color_management_v1.c | 4 ++-- 5 files changed, 14 insertions(+), 16 deletions(-) diff --git a/include/wlr/render/pass.h b/include/wlr/render/pass.h index 8e22bdf8f..1785ee562 100644 --- a/include/wlr/render/pass.h +++ b/include/wlr/render/pass.h @@ -31,8 +31,9 @@ struct wlr_render_timer; struct wlr_buffer_pass_options { /* Timer to measure the duration of the render pass */ struct wlr_render_timer *timer; - /* Color transform to apply to the output of the render pass, - * leave NULL to indicate sRGB/no custom transform */ + /* Color transform to apply to the output of the render pass. + * Leave NULL to indicate the default transform (Gamma 2.2 encoding for + * sRGB monitors) */ struct wlr_color_transform *color_transform; /** Primaries describing the color volume of the destination buffer */ const struct wlr_color_primaries *primaries; diff --git a/render/vulkan/pass.c b/render/vulkan/pass.c index 5555f1197..4cce62eb5 100644 --- a/render/vulkan/pass.c +++ b/render/vulkan/pass.c @@ -57,10 +57,7 @@ static void convert_pixman_box_to_vk_rect(const pixman_box32_t *box, VkRect2D *r } static float color_to_linear(float non_linear) { - // See https://www.w3.org/Graphics/Color/srgb - return (non_linear > 0.04045) ? - pow((non_linear + 0.055) / 1.055, 2.4) : - non_linear / 12.92; + return pow(non_linear, 2.2); } static float color_to_linear_premult(float non_linear, float alpha) { @@ -229,7 +226,7 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { if (pass->color_transform && pass->color_transform->type != COLOR_TRANSFORM_INVERSE_EOTF) { pipeline = render_buffer->two_pass.render_setup->output_pipe_lut3d; } else { - enum wlr_color_transfer_function tf = WLR_COLOR_TRANSFER_FUNCTION_SRGB; + enum wlr_color_transfer_function tf = WLR_COLOR_TRANSFER_FUNCTION_GAMMA22; if (pass->color_transform && pass->color_transform->type == COLOR_TRANSFORM_INVERSE_EOTF) { struct wlr_color_transform_inverse_eotf *inverse_eotf = wlr_color_transform_inverse_eotf_from_base(pass->color_transform); @@ -779,7 +776,7 @@ static void render_pass_add_texture(struct wlr_render_pass *wlr_pass, enum wlr_color_transfer_function tf = options->transfer_function; if (tf == 0) { - tf = WLR_COLOR_TRANSFER_FUNCTION_SRGB; + tf = WLR_COLOR_TRANSFER_FUNCTION_GAMMA22; } bool srgb_image_view = false; @@ -1186,7 +1183,7 @@ struct wlr_vk_render_pass *vulkan_begin_render_pass(struct wlr_vk_renderer *rend } } else { // This is the default when unspecified - inv_eotf = WLR_COLOR_TRANSFER_FUNCTION_SRGB; + inv_eotf = WLR_COLOR_TRANSFER_FUNCTION_GAMMA22; } bool using_linear_pathway = inv_eotf == WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR; diff --git a/types/scene/surface.c b/types/scene/surface.c index aebc00ea2..593537163 100644 --- a/types/scene/surface.c +++ b/types/scene/surface.c @@ -37,12 +37,12 @@ static struct wlr_output *get_surface_frame_pacing_output(struct wlr_surface *su static bool get_tf_preference(enum wlr_color_transfer_function tf) { switch (tf) { - case WLR_COLOR_TRANSFER_FUNCTION_SRGB: + case WLR_COLOR_TRANSFER_FUNCTION_GAMMA22: return 0; case WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ: return 1; + case WLR_COLOR_TRANSFER_FUNCTION_SRGB: case WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR: - case WLR_COLOR_TRANSFER_FUNCTION_GAMMA22: return -1; } abort(); // unreachable @@ -61,7 +61,7 @@ static bool get_primaries_preference(enum wlr_color_named_primaries primaries) { static void get_surface_preferred_image_description(struct wlr_surface *surface, struct wlr_image_description_v1_data *out) { struct wlr_output_image_description preferred = { - .transfer_function = WLR_COLOR_TRANSFER_FUNCTION_SRGB, + .transfer_function = WLR_COLOR_TRANSFER_FUNCTION_GAMMA22, .primaries = WLR_COLOR_NAMED_PRIMARIES_SRGB, }; @@ -249,7 +249,7 @@ static void surface_reconfigure(struct wlr_scene_surface *scene_surface) { opacity = (float)alpha_modifier_state->multiplier; } - enum wlr_color_transfer_function tf = WLR_COLOR_TRANSFER_FUNCTION_SRGB; + enum wlr_color_transfer_function tf = WLR_COLOR_TRANSFER_FUNCTION_GAMMA22; enum wlr_color_named_primaries primaries = WLR_COLOR_NAMED_PRIMARIES_SRGB; const struct wlr_image_description_v1_data *img_desc = wlr_surface_get_image_description_v1_data(surface); diff --git a/types/scene/wlr_scene.c b/types/scene/wlr_scene.c index 9efbe58ad..3d9f96fac 100644 --- a/types/scene/wlr_scene.c +++ b/types/scene/wlr_scene.c @@ -1991,7 +1991,7 @@ static enum scene_direct_scanout_result scene_entry_try_direct_scanout( return SCANOUT_INELIGIBLE; } - if (buffer->transfer_function != 0 && buffer->transfer_function != WLR_COLOR_TRANSFER_FUNCTION_SRGB) { + if (buffer->transfer_function != 0 && buffer->transfer_function != WLR_COLOR_TRANSFER_FUNCTION_GAMMA22) { return SCANOUT_INELIGIBLE; } if (buffer->primaries != 0 && buffer->primaries != WLR_COLOR_NAMED_PRIMARIES_SRGB) { diff --git a/types/wlr_color_management_v1.c b/types/wlr_color_management_v1.c index 54c0a44af..80c357539 100644 --- a/types/wlr_color_management_v1.c +++ b/types/wlr_color_management_v1.c @@ -212,7 +212,7 @@ static void cm_output_handle_get_image_description(struct wl_client *client, } struct wlr_image_description_v1_data data = { - .tf_named = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_SRGB, + .tf_named = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA22, .primaries_named = WP_COLOR_MANAGER_V1_PRIMARIES_SRGB, }; const struct wlr_output_image_description *image_desc = cm_output->output->image_description; @@ -777,7 +777,7 @@ static void manager_handle_get_surface_feedback(struct wl_client *client, surface_feedback->surface = surface; surface_feedback->data = (struct wlr_image_description_v1_data){ - .tf_named = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_SRGB, + .tf_named = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA22, .primaries_named = WP_COLOR_MANAGER_V1_PRIMARIES_SRGB, }; From 6e1c8748ff4e0737133b11259e8fd4e47ab3c795 Mon Sep 17 00:00:00 2001 From: llyyr Date: Sat, 4 Oct 2025 14:48:42 +0530 Subject: [PATCH 143/311] render: introduce bt.1886 transfer function --- backend/drm/atomic.c | 2 ++ include/render/vulkan.h | 3 +++ include/wlr/render/color.h | 1 + render/color.c | 7 +++++++ render/vulkan/pass.c | 6 ++++++ render/vulkan/renderer.c | 6 ++++++ render/vulkan/shaders/output.frag | 11 +++++++++++ render/vulkan/shaders/texture.frag | 11 +++++++++++ types/scene/surface.c | 1 + types/wlr_color_management_v1.c | 4 ++++ 10 files changed, 52 insertions(+) diff --git a/backend/drm/atomic.c b/backend/drm/atomic.c index 7b4b4636a..41773d4f5 100644 --- a/backend/drm/atomic.c +++ b/backend/drm/atomic.c @@ -188,6 +188,8 @@ static uint8_t convert_cta861_eotf(enum wlr_color_transfer_function tf) { abort(); // unsupported case WLR_COLOR_TRANSFER_FUNCTION_GAMMA22: abort(); // unsupported + case WLR_COLOR_TRANSFER_FUNCTION_BT1886: + abort(); // unsupported } abort(); // unreachable } diff --git a/include/render/vulkan.h b/include/render/vulkan.h index 498661070..5358b01c1 100644 --- a/include/render/vulkan.h +++ b/include/render/vulkan.h @@ -154,6 +154,7 @@ enum wlr_vk_texture_transform { WLR_VK_TEXTURE_TRANSFORM_SRGB = 1, WLR_VK_TEXTURE_TRANSFORM_ST2084_PQ = 2, WLR_VK_TEXTURE_TRANSFORM_GAMMA22 = 3, + WLR_VK_TEXTURE_TRANSFORM_BT1886 = 4, }; enum wlr_vk_shader_source { @@ -169,6 +170,7 @@ enum wlr_vk_output_transform { WLR_VK_OUTPUT_TRANSFORM_INVERSE_ST2084_PQ = 2, WLR_VK_OUTPUT_TRANSFORM_LUT3D = 3, WLR_VK_OUTPUT_TRANSFORM_INVERSE_GAMMA22 = 4, + WLR_VK_OUTPUT_TRANSFORM_INVERSE_BT1886 = 5, }; struct wlr_vk_pipeline_key { @@ -202,6 +204,7 @@ struct wlr_vk_render_format_setup { VkPipeline output_pipe_pq; VkPipeline output_pipe_lut3d; VkPipeline output_pipe_gamma22; + VkPipeline output_pipe_bt1886; struct wlr_vk_renderer *renderer; struct wl_list pipelines; // struct wlr_vk_pipeline.link diff --git a/include/wlr/render/color.h b/include/wlr/render/color.h index 7d5b26857..d906e3425 100644 --- a/include/wlr/render/color.h +++ b/include/wlr/render/color.h @@ -29,6 +29,7 @@ enum wlr_color_transfer_function { WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ = 1 << 1, WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR = 1 << 2, WLR_COLOR_TRANSFER_FUNCTION_GAMMA22 = 1 << 3, + WLR_COLOR_TRANSFER_FUNCTION_BT1886 = 1 << 4, }; /** diff --git a/render/color.c b/render/color.c index ae9309dd1..da2f938f8 100644 --- a/render/color.c +++ b/render/color.c @@ -202,6 +202,13 @@ void wlr_color_transfer_function_get_default_luminance(enum wlr_color_transfer_f .reference = 203, }; break; + case WLR_COLOR_TRANSFER_FUNCTION_BT1886: + *lum = (struct wlr_color_luminances){ + .min = 0.01, + .max = 100, + .reference = 100, + }; + break; default: *lum = (struct wlr_color_luminances){ .min = 0.2, diff --git a/render/vulkan/pass.c b/render/vulkan/pass.c index 4cce62eb5..9e212aacc 100644 --- a/render/vulkan/pass.c +++ b/render/vulkan/pass.c @@ -246,6 +246,9 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { case WLR_COLOR_TRANSFER_FUNCTION_GAMMA22: pipeline = render_buffer->two_pass.render_setup->output_pipe_gamma22; break; + case WLR_COLOR_TRANSFER_FUNCTION_BT1886: + pipeline = render_buffer->two_pass.render_setup->output_pipe_bt1886; + break; } struct wlr_color_luminances srgb_lum, dst_lum; @@ -799,6 +802,9 @@ static void render_pass_add_texture(struct wlr_render_pass *wlr_pass, case WLR_COLOR_TRANSFER_FUNCTION_GAMMA22: tex_transform = WLR_VK_TEXTURE_TRANSFORM_GAMMA22; break; + case WLR_COLOR_TRANSFER_FUNCTION_BT1886: + tex_transform = WLR_VK_TEXTURE_TRANSFORM_BT1886; + break; } struct wlr_vk_pipeline *pipe = setup_get_or_create_pipeline( diff --git a/render/vulkan/renderer.c b/render/vulkan/renderer.c index ba14bc2ba..3f880ca6c 100644 --- a/render/vulkan/renderer.c +++ b/render/vulkan/renderer.c @@ -176,6 +176,7 @@ static void destroy_render_format_setup(struct wlr_vk_renderer *renderer, vkDestroyPipeline(dev, setup->output_pipe_pq, NULL); vkDestroyPipeline(dev, setup->output_pipe_lut3d, NULL); vkDestroyPipeline(dev, setup->output_pipe_gamma22, NULL); + vkDestroyPipeline(dev, setup->output_pipe_bt1886, NULL); struct wlr_vk_pipeline *pipeline, *tmp_pipeline; wl_list_for_each_safe(pipeline, tmp_pipeline, &setup->pipelines, link) { @@ -2351,6 +2352,11 @@ static struct wlr_vk_render_format_setup *find_or_create_render_setup( &setup->output_pipe_gamma22, WLR_VK_OUTPUT_TRANSFORM_INVERSE_GAMMA22)) { goto error; } + if (!init_blend_to_output_pipeline( + renderer, setup->render_pass, renderer->output_pipe_layout, + &setup->output_pipe_bt1886, WLR_VK_OUTPUT_TRANSFORM_INVERSE_BT1886)) { + goto error; + } } else { assert(format->vk_srgb); VkAttachmentDescription attachment = { diff --git a/render/vulkan/shaders/output.frag b/render/vulkan/shaders/output.frag index 9785fd225..b9cfcaaa2 100644 --- a/render/vulkan/shaders/output.frag +++ b/render/vulkan/shaders/output.frag @@ -23,6 +23,7 @@ layout (constant_id = 0) const int OUTPUT_TRANSFORM = 0; #define OUTPUT_TRANSFORM_INVERSE_ST2084_PQ 2 #define OUTPUT_TRANSFORM_LUT_3D 3 #define OUTPUT_TRANSFORM_INVERSE_GAMMA22 4 +#define OUTPUT_TRANSFORM_INVERSE_BT1886 5 float linear_channel_to_srgb(float x) { return max(min(x * 12.92, 0.04045), 1.055 * pow(x, 1. / 2.4) - 0.055); @@ -47,6 +48,14 @@ vec3 linear_color_to_pq(vec3 color) { return pow((vec3(c1) + c2 * pow_n) / (vec3(1) + c3 * pow_n), vec3(m)); } +vec3 linear_color_to_bt1886(vec3 color) { + float lb = pow(0.0001, 1.0 / 2.4); + float lw = pow(1.0, 1.0 / 2.4); + float a = pow(lw - lb, 2.4); + float b = lb / (lw - lb); + return pow(color / a, vec3(1.0 / 2.4)) - vec3(b); +} + void main() { vec4 in_color = subpassLoad(in_color).rgba; @@ -74,6 +83,8 @@ void main() { rgb = linear_color_to_srgb(rgb); } else if (OUTPUT_TRANSFORM == OUTPUT_TRANSFORM_INVERSE_GAMMA22) { rgb = pow(rgb, vec3(1. / 2.2)); + } else if (OUTPUT_TRANSFORM == OUTPUT_TRANSFORM_INVERSE_BT1886) { + rgb = linear_color_to_bt1886(rgb); } // Back to pre-multiplied alpha diff --git a/render/vulkan/shaders/texture.frag b/render/vulkan/shaders/texture.frag index bb73681f5..b7b78b19a 100644 --- a/render/vulkan/shaders/texture.frag +++ b/render/vulkan/shaders/texture.frag @@ -19,6 +19,7 @@ layout (constant_id = 0) const int TEXTURE_TRANSFORM = 0; #define TEXTURE_TRANSFORM_SRGB 1 #define TEXTURE_TRANSFORM_ST2084_PQ 2 #define TEXTURE_TRANSFORM_GAMMA22 3 +#define TEXTURE_TRANSFORM_BT1886 4 float srgb_channel_to_linear(float x) { return mix(x / 12.92, @@ -45,6 +46,14 @@ vec3 pq_color_to_linear(vec3 color) { return pow(num / denom, vec3(inv_m1)); } +vec3 bt1886_color_to_linear(vec3 color) { + float lb = pow(0.0001, 1.0 / 2.4); + float lw = pow(1.0, 1.0 / 2.4); + float a = pow(lw - lb, 2.4); + float b = lb / (lw - lb); + return a * pow(color + vec3(b), vec3(2.4)); +} + void main() { vec4 in_color = textureLod(tex, uv, 0); @@ -63,6 +72,8 @@ void main() { rgb = pq_color_to_linear(rgb); } else if (TEXTURE_TRANSFORM == TEXTURE_TRANSFORM_GAMMA22) { rgb = pow(rgb, vec3(2.2)); + } else if (TEXTURE_TRANSFORM == TEXTURE_TRANSFORM_BT1886) { + rgb = bt1886_color_to_linear(rgb); } rgb *= data.luminance_multiplier; diff --git a/types/scene/surface.c b/types/scene/surface.c index 593537163..68a445b98 100644 --- a/types/scene/surface.c +++ b/types/scene/surface.c @@ -41,6 +41,7 @@ static bool get_tf_preference(enum wlr_color_transfer_function tf) { return 0; case WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ: return 1; + case WLR_COLOR_TRANSFER_FUNCTION_BT1886: case WLR_COLOR_TRANSFER_FUNCTION_SRGB: case WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR: return -1; diff --git a/types/wlr_color_management_v1.c b/types/wlr_color_management_v1.c index 80c357539..c69a69c81 100644 --- a/types/wlr_color_management_v1.c +++ b/types/wlr_color_management_v1.c @@ -995,6 +995,8 @@ wlr_color_manager_v1_transfer_function_to_wlr(enum wp_color_manager_v1_transfer_ return WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR; case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA22: return WLR_COLOR_TRANSFER_FUNCTION_GAMMA22; + case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_BT1886: + return WLR_COLOR_TRANSFER_FUNCTION_BT1886; default: abort(); } @@ -1011,6 +1013,8 @@ wlr_color_manager_v1_transfer_function_from_wlr(enum wlr_color_transfer_function return WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR; case WLR_COLOR_TRANSFER_FUNCTION_GAMMA22: return WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA22; + case WLR_COLOR_TRANSFER_FUNCTION_BT1886: + return WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_BT1886; } abort(); } From 5529aae3e65aa182d88cecc1efa4b10a20b553eb Mon Sep 17 00:00:00 2001 From: llyyr Date: Sun, 5 Oct 2025 22:56:33 +0530 Subject: [PATCH 144/311] wlr_scene: fix direct scanout for gamma2.2 buffers Fixes incorrectly rejecting scanout for gamma2.2 buffers when the output has no image description set. This happens on `hdr off` mode on sway. Also refactor the scanout check into its own function while at it to make it easier to follow. --- types/scene/wlr_scene.c | 35 ++++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/types/scene/wlr_scene.c b/types/scene/wlr_scene.c index 3d9f96fac..a06b641e3 100644 --- a/types/scene/wlr_scene.c +++ b/types/scene/wlr_scene.c @@ -1925,6 +1925,27 @@ static void scene_buffer_send_dmabuf_feedback(const struct wlr_scene *scene, wlr_linux_dmabuf_feedback_v1_finish(&feedback); } +static bool color_management_is_scanout_allowed(const struct wlr_output_image_description *img_desc, + const struct wlr_scene_buffer *buffer) { + // Disallow scanout if the output has colorimetry information but buffer + // doesn't; allow it only if the output also lacks it. + if (buffer->transfer_function == 0 && buffer->primaries == 0) { + return img_desc == NULL; + } + + // If the output has colorimetry information, the buffer must match it for + // direct scanout to be allowed. + if (img_desc != NULL) { + return img_desc->transfer_function == buffer->transfer_function && + img_desc->primaries == buffer->primaries; + } + // If the output doesn't have colorimetry image description set, we can only + // scan out buffers with default colorimetry (gamma2.2 transfer and sRGB + // primaries) used in wlroots. + return buffer->transfer_function == WLR_COLOR_TRANSFER_FUNCTION_GAMMA22 && + buffer->primaries == WLR_COLOR_NAMED_PRIMARIES_SRGB; +} + enum scene_direct_scanout_result { // This scene node is not a candidate for scanout SCANOUT_INELIGIBLE, @@ -1982,19 +2003,7 @@ static enum scene_direct_scanout_result scene_entry_try_direct_scanout( } const struct wlr_output_image_description *img_desc = output_pending_image_description(scene_output->output, state); - if (buffer->transfer_function != 0 || buffer->primaries != 0) { - if (img_desc == NULL || img_desc->transfer_function != buffer->transfer_function || - img_desc->primaries != buffer->primaries) { - return SCANOUT_INELIGIBLE; - } - } else if (img_desc != NULL) { - return SCANOUT_INELIGIBLE; - } - - if (buffer->transfer_function != 0 && buffer->transfer_function != WLR_COLOR_TRANSFER_FUNCTION_GAMMA22) { - return SCANOUT_INELIGIBLE; - } - if (buffer->primaries != 0 && buffer->primaries != WLR_COLOR_NAMED_PRIMARIES_SRGB) { + if (!color_management_is_scanout_allowed(img_desc, buffer)) { return SCANOUT_INELIGIBLE; } From 03e7966650c29cf8e563c8fcacc1ae1edf2f3efa Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Thu, 16 Oct 2025 11:04:25 +0200 Subject: [PATCH 145/311] ci: fix VKMS lookup after faux bus migration VKMS has been migrated to the new faux bus. This causes breakage in CI, because we used the platform bus to find the right device. udev hasn't been updated yet to support the faux bus, so just use sysfs instead. --- .builds/archlinux.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.builds/archlinux.yml b/.builds/archlinux.yml index 8457ed585..fae04ab31 100644 --- a/.builds/archlinux.yml +++ b/.builds/archlinux.yml @@ -41,9 +41,10 @@ tasks: cd wlroots/build-gcc/tinywl sudo modprobe vkms udevadm settle + card="/dev/dri/$(ls /sys/devices/faux/vkms/drm/ | grep ^card)" export WLR_BACKENDS=drm export WLR_RENDERER=pixman - export WLR_DRM_DEVICES=/dev/dri/by-path/platform-vkms-card + export WLR_DRM_DEVICES="$card" export UBSAN_OPTIONS=halt_on_error=1 - sudo chmod ugo+rw /dev/dri/by-path/platform-vkms-card + sudo chmod ugo+rw "$card" sudo -E seatd-launch -- ./tinywl -s 'kill $PPID' || [ $? = 143 ] From 06275103f249cd2954630e59383342e102a6c1a3 Mon Sep 17 00:00:00 2001 From: Furkan Sahin Date: Thu, 9 Oct 2025 20:02:32 -0400 Subject: [PATCH 146/311] input-method-v2: Destroy keyboard grab before input method Fixes race condition in where the keyboard grab tries to reference the input manager after it's been set to null. --- types/wlr_input_method_v2.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/types/wlr_input_method_v2.c b/types/wlr_input_method_v2.c index 0a521df48..a9501654c 100644 --- a/types/wlr_input_method_v2.c +++ b/types/wlr_input_method_v2.c @@ -56,6 +56,7 @@ static void input_method_destroy(struct wlr_input_method_v2 *input_method) { popup_surface, tmp, &input_method->popup_surfaces, link) { popup_surface_destroy(popup_surface); } + wlr_input_method_keyboard_grab_v2_destroy(input_method->keyboard_grab); wl_signal_emit_mutable(&input_method->events.destroy, NULL); assert(wl_list_empty(&input_method->events.commit.listener_list)); @@ -65,7 +66,6 @@ static void input_method_destroy(struct wlr_input_method_v2 *input_method) { wl_list_remove(wl_resource_get_link(input_method->resource)); wl_list_remove(&input_method->seat_client_destroy.link); - wlr_input_method_keyboard_grab_v2_destroy(input_method->keyboard_grab); input_state_reset(&input_method->pending); input_state_reset(&input_method->current); free(input_method); @@ -271,8 +271,7 @@ void wlr_input_method_keyboard_grab_v2_destroy( if (!keyboard_grab) { return; } - wl_signal_emit_mutable(&keyboard_grab->events.destroy, keyboard_grab); - + wl_signal_emit_mutable(&keyboard_grab->events.destroy, NULL); assert(wl_list_empty(&keyboard_grab->events.destroy.listener_list)); keyboard_grab->input_method->keyboard_grab = NULL; From 19c5d22beb1af30e5fcd831751f404caafdcd2f5 Mon Sep 17 00:00:00 2001 From: tokyo4j Date: Tue, 23 Sep 2025 23:44:52 +0900 Subject: [PATCH 147/311] util/box.c: use 1/256 instead of 1/65536 in wlr_box_closest_point() This fixes the issue that a scrollbar in a maximized GTK/Chromium window cannot be dragged when cursor is on the right/bottom edge of the output. The issue was caused by rounding in `wl_fixed_from_double()` ([1]); if `wlr_cursor_move()` constrains the x-position of the cursor to `(output width)-1/65536`, `wl_fixed_from_double()` converts it to just `(output width)`, which is perceived as outside of the window by GTK/Chromium. Using 1/256 (minimal unit of `wl_fixed_t`) instead of 1/65536 avoids this rounding issue. [1]: https://gitlab.freedesktop.org/wayland/wayland/-/commit/f246e619d17deb92f414315d1747a9b7aca659b9 --- util/box.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/util/box.c b/util/box.c index a9b42579a..aae09888f 100644 --- a/util/box.c +++ b/util/box.c @@ -19,16 +19,15 @@ void wlr_box_closest_point(const struct wlr_box *box, double x, double y, // // In order to be consistent with e.g. wlr_box_contains_point(), // this function returns a point inside the bottom and right edges - // of the box by at least 1/65536 of a unit (pixel). 1/65536 is + // of the box by at least 1/256 of a unit (pixel). 1/256 is // small enough to avoid a "dead zone" with high-resolution mice - // but large enough to avoid rounding to zero (due to loss of - // significant digits) in simple floating-point calculations. + // but large enough to avoid rounding to zero in wl_fixed_from_double(). // find the closest x point if (x < box->x) { *dest_x = box->x; - } else if (x > box->x + box->width - 1/65536.0) { - *dest_x = box->x + box->width - 1/65536.0; + } else if (x > box->x + box->width - 1/256.0) { + *dest_x = box->x + box->width - 1/256.0; } else { *dest_x = x; } @@ -36,8 +35,8 @@ void wlr_box_closest_point(const struct wlr_box *box, double x, double y, // find closest y point if (y < box->y) { *dest_y = box->y; - } else if (y > box->y + box->height - 1/65536.0) { - *dest_y = box->y + box->height - 1/65536.0; + } else if (y > box->y + box->height - 1/256.0) { + *dest_y = box->y + box->height - 1/256.0; } else { *dest_y = y; } From 6d63871f059192b15d2fa0dfacfb391709f4952d Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Thu, 16 Oct 2025 10:42:20 +0200 Subject: [PATCH 148/311] linux_drm_syncobj_v1: fix use-after-free in surface_commit_destroy() surface_commit_destroy() accesses a field from struct wlr_linux_drm_syncobj_surface_v1, however that struct may have been free'd earlier: ==1103==ERROR: AddressSanitizer: heap-use-after-free on address 0x7cdef7a6e288 at pc 0x7feefaac335a bp 0x7ffc4de8f570 sp 0x7ffc4de8f560 READ of size 8 at 0x7cdef7a6e288 thread T0 #0 0x7feefaac3359 in surface_commit_destroy ../subprojects/wlroots/types/wlr_linux_drm_syncobj_v1.c:195 #1 0x7feefaac34cd in surface_commit_handle_surface_destroy ../subprojects/wlroots/types/wlr_linux_drm_syncobj_v1.c:211 #2 0x7feefbd194cf in wl_signal_emit_mutable (/usr/lib/libwayland-server.so.0+0x84cf) (BuildId: b9664217748f523995e3f855fa197cf8e59942d1) #3 0x7feefaa52b22 in surface_handle_resource_destroy ../subprojects/wlroots/types/wlr_compositor.c:730 #4 0x7feefbd1bb9f (/usr/lib/libwayland-server.so.0+0xab9f) (BuildId: b9664217748f523995e3f855fa197cf8e59942d1) #5 0x7feefaa46a18 in surface_handle_destroy ../subprojects/wlroots/types/wlr_compositor.c:65 #6 0x7feef89afac5 (/usr/lib/libffi.so.8+0x7ac5) (BuildId: d5e3b0d8921923f35438adefa9f864745abc5e90) #7 0x7feef89ac76a (/usr/lib/libffi.so.8+0x476a) (BuildId: d5e3b0d8921923f35438adefa9f864745abc5e90) #8 0x7feef89af06d in ffi_call (/usr/lib/libffi.so.8+0x706d) (BuildId: d5e3b0d8921923f35438adefa9f864745abc5e90) #9 0x7feefbd17531 (/usr/lib/libwayland-server.so.0+0x6531) (BuildId: b9664217748f523995e3f855fa197cf8e59942d1) #10 0x7feefbd1cd2f (/usr/lib/libwayland-server.so.0+0xbd2f) (BuildId: b9664217748f523995e3f855fa197cf8e59942d1) #11 0x7feefbd1b181 in wl_event_loop_dispatch (/usr/lib/libwayland-server.so.0+0xa181) (BuildId: b9664217748f523995e3f855fa197cf8e59942d1) #12 0x7feefbd1d296 in wl_display_run (/usr/lib/libwayland-server.so.0+0xc296) (BuildId: b9664217748f523995e3f855fa197cf8e59942d1) #13 0x555bf0a55a40 in server_run ../sway/server.c:615 #14 0x555bf0a4a654 in main ../sway/main.c:376 #15 0x7feef9227674 (/usr/lib/libc.so.6+0x27674) (BuildId: 4fe011c94a88e8aeb6f2201b9eb369f42b4a1e9e) #16 0x7feef9227728 in __libc_start_main (/usr/lib/libc.so.6+0x27728) (BuildId: 4fe011c94a88e8aeb6f2201b9eb369f42b4a1e9e) #17 0x555bf0a03f54 in _start (/home/leo/code/stuff/sway/build/sway/sway+0x390f54) (BuildId: e3d4e653af1aa0885f0426c403e16fc87c086d33) 0x7cdef7a6e288 is located 8 bytes inside of 176-byte region [0x7cdef7a6e280,0x7cdef7a6e330) freed by thread T0 here: #0 0x7feefb71f79d in free /usr/src/debug/gcc/gcc/libsanitizer/asan/asan_malloc_linux.cpp:51 #1 0x7feefaac29f1 in surface_destroy ../subprojects/wlroots/types/wlr_linux_drm_syncobj_v1.c:84 #2 0x7feefaac2e47 in surface_handle_resource_destroy ../subprojects/wlroots/types/wlr_linux_drm_syncobj_v1.c:143 #3 0x7feefbd1bb9f (/usr/lib/libwayland-server.so.0+0xab9f) (BuildId: b9664217748f523995e3f855fa197cf8e59942d1) #4 0x7feefaac2a12 in surface_handle_destroy ../subprojects/wlroots/types/wlr_linux_drm_syncobj_v1.c:89 #5 0x7feef89afac5 (/usr/lib/libffi.so.8+0x7ac5) (BuildId: d5e3b0d8921923f35438adefa9f864745abc5e90) previously allocated by thread T0 here: #0 0x7feefb7205dd in calloc /usr/src/debug/gcc/gcc/libsanitizer/asan/asan_malloc_linux.cpp:74 #1 0x7feefaac4abd in manager_handle_get_surface ../subprojects/wlroots/types/wlr_linux_drm_syncobj_v1.c:313 #2 0x7feef89afac5 (/usr/lib/libffi.so.8+0x7ac5) (BuildId: d5e3b0d8921923f35438adefa9f864745abc5e90) Fix this by storing the struct wlr_surface in the field. Closes: https://github.com/swaywm/sway/issues/8917 --- types/wlr_linux_drm_syncobj_v1.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/types/wlr_linux_drm_syncobj_v1.c b/types/wlr_linux_drm_syncobj_v1.c index 7873a09c4..988d44e01 100644 --- a/types/wlr_linux_drm_syncobj_v1.c +++ b/types/wlr_linux_drm_syncobj_v1.c @@ -26,7 +26,7 @@ struct wlr_linux_drm_syncobj_surface_v1 { }; struct wlr_linux_drm_syncobj_surface_v1_commit { - struct wlr_linux_drm_syncobj_surface_v1 *surface; + struct wlr_surface *surface; struct wlr_drm_syncobj_timeline_waiter waiter; uint32_t cached_seq; @@ -192,7 +192,7 @@ static struct wlr_linux_drm_syncobj_surface_v1 *surface_from_wlr_surface( } static void surface_commit_destroy(struct wlr_linux_drm_syncobj_surface_v1_commit *commit) { - wlr_surface_unlock_cached(commit->surface->surface, commit->cached_seq); + wlr_surface_unlock_cached(commit->surface, commit->cached_seq); wl_list_remove(&commit->surface_destroy.link); wlr_drm_syncobj_timeline_waiter_finish(&commit->waiter); free(commit); @@ -237,7 +237,7 @@ static bool lock_surface_commit(struct wlr_linux_drm_syncobj_surface_v1 *surface return false; } - commit->surface = surface; + commit->surface = surface->surface; commit->cached_seq = wlr_surface_lock_pending(surface->surface); commit->surface_destroy.notify = surface_commit_handle_surface_destroy; From d786e07899481dd970025ffef09a18eb726cd41d Mon Sep 17 00:00:00 2001 From: Furkan Sahin Date: Tue, 14 Oct 2025 17:46:50 -0400 Subject: [PATCH 149/311] backend/session: use device `boot_display` shouldn't need to check for `boot_vga` if newer, more general sysfs `boot_display` is set. closes https://gitlab.freedesktop.org/wlroots/wlroots/-/issues/4016 --- backend/session/session.c | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/backend/session/session.c b/backend/session/session.c index 48f4ab187..32522fb42 100644 --- a/backend/session/session.c +++ b/backend/session/session.c @@ -519,8 +519,6 @@ ssize_t wlr_session_find_gpus(struct wlr_session *session, break; } - bool is_boot_vga = false; - const char *path = udev_list_entry_get_name(entry); struct udev_device *dev = udev_device_new_from_syspath(session->udev, path); if (!dev) { @@ -536,14 +534,20 @@ ssize_t wlr_session_find_gpus(struct wlr_session *session, continue; } - // This is owned by 'dev', so we don't need to free it - struct udev_device *pci = - udev_device_get_parent_with_subsystem_devtype(dev, "pci", NULL); + bool is_primary = false; + const char *boot_display = udev_device_get_sysattr_value(dev, "boot_display"); + if (boot_display && strcmp(boot_display, "1") == 0) { + is_primary = true; + } else { + // This is owned by 'dev', so we don't need to free it + struct udev_device *pci = + udev_device_get_parent_with_subsystem_devtype(dev, "pci", NULL); - if (pci) { - const char *id = udev_device_get_sysattr_value(pci, "boot_vga"); - if (id && strcmp(id, "1") == 0) { - is_boot_vga = true; + if (pci) { + const char *id = udev_device_get_sysattr_value(pci, "boot_vga"); + if (id && strcmp(id, "1") == 0) { + is_primary = true; + } } } @@ -557,7 +561,7 @@ ssize_t wlr_session_find_gpus(struct wlr_session *session, udev_device_unref(dev); ret[i] = wlr_dev; - if (is_boot_vga) { + if (is_primary) { struct wlr_device *tmp = ret[0]; ret[0] = ret[i]; ret[i] = tmp; From 3d36ab921114e17f2538d8260876c62786115b1a Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sun, 15 Jun 2025 15:00:21 +0200 Subject: [PATCH 150/311] render/color: add wlr_color_transform_eval() Makes it so the Vulkan renderer can handle arbitrary color transforms, and doesn't need to be updated each time a new one is added. --- include/render/color.h | 6 --- include/wlr/render/color.h | 6 +++ render/color.c | 76 +++++++++++++++++++++++++++++++++++++- render/vulkan/pass.c | 19 +--------- 4 files changed, 82 insertions(+), 25 deletions(-) diff --git a/include/render/color.h b/include/render/color.h index 8730ac6e9..5dc6481ab 100644 --- a/include/render/color.h +++ b/include/render/color.h @@ -72,12 +72,6 @@ struct wlr_color_transform_inverse_eotf *wlr_color_transform_inverse_eotf_from_b struct wlr_color_transform_lut_3x1d *color_transform_lut_3x1d_from_base( struct wlr_color_transform *tr); -/** - * Evaluate a 3x1D LUT color transform for a given RGB triplet. - */ -void color_transform_lut_3x1d_eval(struct wlr_color_transform_lut_3x1d *tr, - float out[static 3], const float in[static 3]); - /** * Obtain primaries values from a well-known primaries name. */ diff --git a/include/wlr/render/color.h b/include/wlr/render/color.h index d906e3425..5f2ad36b5 100644 --- a/include/wlr/render/color.h +++ b/include/wlr/render/color.h @@ -152,4 +152,10 @@ struct wlr_color_transform *wlr_color_transform_ref(struct wlr_color_transform * */ void wlr_color_transform_unref(struct wlr_color_transform *tr); +/** + * Evaluate a color transform for a given RGB triplet. + */ +void wlr_color_transform_eval(struct wlr_color_transform *tr, + float out[static 3], const float in[static 3]); + #endif diff --git a/render/color.c b/render/color.c index da2f938f8..287cda76f 100644 --- a/render/color.c +++ b/render/color.c @@ -108,6 +108,65 @@ struct wlr_color_transform_lut_3x1d *color_transform_lut_3x1d_from_base( return lut_3x1d; } +static float srgb_eval_inverse_eotf(float x) { + // See https://www.w3.org/Graphics/Color/srgb + if (x <= 0.0031308) { + return 12.92 * x; + } else { + return 1.055 * powf(x, 1.0 / 2.4) - 0.055; + } +} + +static float st2084_pq_eval_inverse_eotf(float x) { + // H.273 TransferCharacteristics code point 16 + float c1 = 0.8359375; + float c2 = 18.8515625; + float c3 = 18.6875; + float m = 78.84375; + float n = 0.1593017578125; + if (x < 0) { + x = 0; + } + if (x > 1) { + x = 1; + } + float pow_n = powf(x, n); + return powf((c1 + c2 * pow_n) / (1 + c3 * pow_n), m); +} + +static float bt1886_eval_inverse_eotf(float x) { + float lb = powf(0.0001, 1.0 / 2.4); + float lw = powf(1.0, 1.0 / 2.4); + float a = powf(lw - lb, 2.4); + float b = lb / (lw - lb); + return powf(x / a, 1.0 / 2.4) - b; +} + +static float transfer_function_eval_inverse_eotf( + enum wlr_color_transfer_function tf, float x) { + switch (tf) { + case WLR_COLOR_TRANSFER_FUNCTION_SRGB: + return srgb_eval_inverse_eotf(x); + case WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ: + return st2084_pq_eval_inverse_eotf(x); + case WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR: + return x; + case WLR_COLOR_TRANSFER_FUNCTION_GAMMA22: + return powf(x, 1.0 / 2.2); + case WLR_COLOR_TRANSFER_FUNCTION_BT1886: + return bt1886_eval_inverse_eotf(x); + } + abort(); // unreachable +} + +static void color_transform_inverse_eotf_eval( + struct wlr_color_transform_inverse_eotf *tr, + float out[static 3], const float in[static 3]) { + for (size_t i = 0; i < 3; i++) { + out[i] = transfer_function_eval_inverse_eotf(tr->tf, in[i]); + } +} + static float lut_1d_get(const uint16_t *lut, size_t len, size_t i) { if (i >= len) { i = len - 1; @@ -125,13 +184,28 @@ static float lut_1d_eval(const uint16_t *lut, size_t len, float x) { return a * (1 - frac_part) + b * frac_part; } -void color_transform_lut_3x1d_eval(struct wlr_color_transform_lut_3x1d *tr, +static void color_transform_lut_3x1d_eval(struct wlr_color_transform_lut_3x1d *tr, float out[static 3], const float in[static 3]) { for (size_t i = 0; i < 3; i++) { out[i] = lut_1d_eval(&tr->lut_3x1d[tr->dim * i], tr->dim, in[i]); } } +void wlr_color_transform_eval(struct wlr_color_transform *tr, + float out[static 3], const float in[static 3]) { + switch (tr->type) { + case COLOR_TRANSFORM_INVERSE_EOTF: + color_transform_inverse_eotf_eval(wlr_color_transform_inverse_eotf_from_base(tr), out, in); + break; + case COLOR_TRANSFORM_LCMS2: + color_transform_lcms2_eval(color_transform_lcms2_from_base(tr), out, in); + break; + case COLOR_TRANSFORM_LUT_3X1D: + color_transform_lut_3x1d_eval(color_transform_lut_3x1d_from_base(tr), out, in); + break; + } +} + void wlr_color_primaries_from_named(struct wlr_color_primaries *out, enum wlr_color_named_primaries named) { switch (named) { diff --git a/render/vulkan/pass.c b/render/vulkan/pass.c index 9e212aacc..398ee2104 100644 --- a/render/vulkan/pass.c +++ b/render/vulkan/pass.c @@ -964,19 +964,6 @@ static bool create_3d_lut_image(struct wlr_vk_renderer *renderer, *ds = VK_NULL_HANDLE; *ds_pool = NULL; - struct wlr_color_transform_lcms2 *tr_lcms2 = NULL; - struct wlr_color_transform_lut_3x1d *tr_lut_3x1d = NULL; - switch (tr->type) { - case COLOR_TRANSFORM_INVERSE_EOTF: - abort(); // unreachable - case COLOR_TRANSFORM_LCMS2: - tr_lcms2 = color_transform_lcms2_from_base(tr); - break; - case COLOR_TRANSFORM_LUT_3X1D: - tr_lut_3x1d = color_transform_lut_3x1d_from_base(tr); - break; - } - // R32G32B32 is not a required Vulkan format // TODO: use it when available VkFormat format = VK_FORMAT_R32G32B32A32_SFLOAT; @@ -1074,11 +1061,7 @@ static bool create_3d_lut_image(struct wlr_vk_renderer *renderer, b_index * sample_range, }; float rgb_out[3]; - if (tr_lcms2 != NULL) { - color_transform_lcms2_eval(tr_lcms2, rgb_out, rgb_in); - } else { - color_transform_lut_3x1d_eval(tr_lut_3x1d, rgb_out, rgb_in); - } + wlr_color_transform_eval(tr, rgb_out, rgb_in); dst[dst_offset] = rgb_out[0]; dst[dst_offset + 1] = rgb_out[1]; From 0b58bddf1370a173d53bc2ed51c7b46294252c5c Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sun, 15 Jun 2025 15:03:18 +0200 Subject: [PATCH 151/311] render/color: add wlr_color_transform_pipeline Useful to apply multiple transforms in sequence, e.g. sRGB inverse EOTF followed by gamma LUTs. --- include/render/color.h | 8 +++++++ include/wlr/render/color.h | 7 ++++++ render/color.c | 45 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+) diff --git a/include/render/color.h b/include/render/color.h index 5dc6481ab..128b345e1 100644 --- a/include/render/color.h +++ b/include/render/color.h @@ -9,6 +9,7 @@ enum wlr_color_transform_type { COLOR_TRANSFORM_INVERSE_EOTF, COLOR_TRANSFORM_LCMS2, COLOR_TRANSFORM_LUT_3X1D, + COLOR_TRANSFORM_PIPELINE, }; struct wlr_color_transform { @@ -39,6 +40,13 @@ struct wlr_color_transform_lut_3x1d { size_t dim; }; +struct wlr_color_transform_pipeline { + struct wlr_color_transform base; + + struct wlr_color_transform **transforms; + size_t len; +}; + void wlr_color_transform_init(struct wlr_color_transform *tr, enum wlr_color_transform_type type); diff --git a/include/wlr/render/color.h b/include/wlr/render/color.h index 5f2ad36b5..bc3baf181 100644 --- a/include/wlr/render/color.h +++ b/include/wlr/render/color.h @@ -141,6 +141,13 @@ struct wlr_color_transform *wlr_color_transform_init_linear_to_inverse_eotf( struct wlr_color_transform *wlr_color_transform_init_lut_3x1d(size_t dim, const uint16_t *r, const uint16_t *g, const uint16_t *b); +/** + * Initialize a color transformation to apply a sequence of color transforms + * one after another. + */ +struct wlr_color_transform *wlr_color_transform_init_pipeline( + struct wlr_color_transform **transforms, size_t len); + /** * Increase the reference count of the color transform by 1. */ diff --git a/render/color.c b/render/color.c index 287cda76f..0a1a67be3 100644 --- a/render/color.c +++ b/render/color.c @@ -62,6 +62,33 @@ struct wlr_color_transform *wlr_color_transform_init_lut_3x1d(size_t dim, return &tx->base; } +struct wlr_color_transform *wlr_color_transform_init_pipeline( + struct wlr_color_transform **transforms, size_t len) { + assert(len > 0); + + struct wlr_color_transform **copy = calloc(len, sizeof(copy[0])); + if (copy == NULL) { + return NULL; + } + + struct wlr_color_transform_pipeline *tx = calloc(1, sizeof(*tx)); + if (!tx) { + free(copy); + return NULL; + } + wlr_color_transform_init(&tx->base, COLOR_TRANSFORM_PIPELINE); + + // TODO: flatten nested pipeline transforms + for (size_t i = 0; i < len; i++) { + copy[i] = wlr_color_transform_ref(transforms[i]); + } + + tx->transforms = copy; + tx->len = len; + + return &tx->base; +} + static void color_transform_destroy(struct wlr_color_transform *tr) { switch (tr->type) { case COLOR_TRANSFORM_INVERSE_EOTF: @@ -73,6 +100,14 @@ static void color_transform_destroy(struct wlr_color_transform *tr) { struct wlr_color_transform_lut_3x1d *lut_3x1d = color_transform_lut_3x1d_from_base(tr); free(lut_3x1d->lut_3x1d); break; + case COLOR_TRANSFORM_PIPELINE:; + struct wlr_color_transform_pipeline *pipeline = + wl_container_of(tr, pipeline, base); + for (size_t i = 0; i < pipeline->len; i++) { + wlr_color_transform_unref(pipeline->transforms[i]); + } + free(pipeline->transforms); + break; } wlr_addon_set_finish(&tr->addons); free(tr); @@ -203,6 +238,16 @@ void wlr_color_transform_eval(struct wlr_color_transform *tr, case COLOR_TRANSFORM_LUT_3X1D: color_transform_lut_3x1d_eval(color_transform_lut_3x1d_from_base(tr), out, in); break; + case COLOR_TRANSFORM_PIPELINE:; + struct wlr_color_transform_pipeline *pipeline = + wl_container_of(tr, pipeline, base); + float color[3]; + memcpy(color, in, sizeof(color)); + for (size_t i = 0; i < pipeline->len; i++) { + wlr_color_transform_eval(pipeline->transforms[i], color, color); + } + memcpy(out, color, sizeof(color)); + break; } } From 74ce6c22a54f28abcaaef743d739da78fb853e85 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Mon, 23 Jun 2025 09:15:10 +0200 Subject: [PATCH 152/311] output: check for color transform no-op changes This allows callers to always set this state and not care whether the output currently has the same color transform applied. --- include/wlr/types/wlr_output.h | 1 + types/output/output.c | 17 +++++++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/include/wlr/types/wlr_output.h b/include/wlr/types/wlr_output.h index 6a0dd0455..2ae11a4d3 100644 --- a/include/wlr/types/wlr_output.h +++ b/include/wlr/types/wlr_output.h @@ -267,6 +267,7 @@ struct wlr_output { struct { struct wl_listener display_destroy; struct wlr_output_image_description image_description_value; + struct wlr_color_transform *color_transform; } WLR_PRIVATE; }; diff --git a/types/output/output.c b/types/output/output.c index 1fb0347a0..ca2e55538 100644 --- a/types/output/output.c +++ b/types/output/output.c @@ -242,6 +242,15 @@ static void output_apply_state(struct wlr_output *output, } } + if (state->committed & WLR_OUTPUT_STATE_COLOR_TRANSFORM) { + wlr_color_transform_unref(output->color_transform); + if (state->color_transform != NULL) { + output->color_transform = wlr_color_transform_ref(state->color_transform); + } else { + output->color_transform = NULL; + } + } + bool geometry_updated = state->committed & (WLR_OUTPUT_STATE_MODE | WLR_OUTPUT_STATE_TRANSFORM | WLR_OUTPUT_STATE_SUBPIXEL); @@ -407,6 +416,7 @@ void wlr_output_finish(struct wlr_output *output) { wlr_swapchain_destroy(output->cursor_swapchain); wlr_buffer_unlock(output->cursor_front_buffer); + wlr_color_transform_unref(output->color_transform); wlr_swapchain_destroy(output->swapchain); @@ -515,8 +525,7 @@ const struct wlr_output_image_description *output_pending_image_description( * Returns a bitfield of the unchanged fields. * * Some fields are not checked: damage always changes in-between frames, the - * gamma LUT is too expensive to check, the contents of the buffer might have - * changed, etc. + * contents of the buffer might have changed, etc. */ static uint32_t output_compare_state(struct wlr_output *output, const struct wlr_output_state *state) { @@ -562,6 +571,10 @@ static uint32_t output_compare_state(struct wlr_output *output, output->subpixel == state->subpixel) { fields |= WLR_OUTPUT_STATE_SUBPIXEL; } + if ((state->committed & WLR_OUTPUT_STATE_COLOR_TRANSFORM) && + output->color_transform == state->color_transform) { + fields |= WLR_OUTPUT_STATE_COLOR_TRANSFORM; + } return fields; } From 91f4890ec27baab6f0df598e842cdf127cc6304c Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Wed, 2 Jul 2025 18:28:06 +0200 Subject: [PATCH 153/311] gamma_control_v1: add wlr_gamma_control_v1_get_color_transform() --- include/wlr/types/wlr_gamma_control_v1.h | 2 ++ types/wlr_gamma_control_v1.c | 19 ++++++++++++++----- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/include/wlr/types/wlr_gamma_control_v1.h b/include/wlr/types/wlr_gamma_control_v1.h index b3a70f11e..4b0948964 100644 --- a/include/wlr/types/wlr_gamma_control_v1.h +++ b/include/wlr/types/wlr_gamma_control_v1.h @@ -49,6 +49,8 @@ struct wlr_gamma_control_v1 *wlr_gamma_control_manager_v1_get_control( struct wlr_gamma_control_manager_v1 *manager, struct wlr_output *output); bool wlr_gamma_control_v1_apply(struct wlr_gamma_control_v1 *gamma_control, struct wlr_output_state *output_state); +struct wlr_color_transform *wlr_gamma_control_v1_get_color_transform( + struct wlr_gamma_control_v1 *gamma_control); void wlr_gamma_control_v1_send_failed_and_destroy(struct wlr_gamma_control_v1 *gamma_control); #endif diff --git a/types/wlr_gamma_control_v1.c b/types/wlr_gamma_control_v1.c index 207d1977f..04d73c2e5 100644 --- a/types/wlr_gamma_control_v1.c +++ b/types/wlr_gamma_control_v1.c @@ -262,15 +262,24 @@ struct wlr_gamma_control_v1 *wlr_gamma_control_manager_v1_get_control( return NULL; } +struct wlr_color_transform *wlr_gamma_control_v1_get_color_transform( + struct wlr_gamma_control_v1 *gamma_control) { + if (gamma_control == NULL || gamma_control->table == NULL) { + return NULL; + } + + const uint16_t *r = gamma_control->table; + const uint16_t *g = gamma_control->table + gamma_control->ramp_size; + const uint16_t *b = gamma_control->table + 2 * gamma_control->ramp_size; + + return wlr_color_transform_init_lut_3x1d(gamma_control->ramp_size, r, g, b); +} + bool wlr_gamma_control_v1_apply(struct wlr_gamma_control_v1 *gamma_control, struct wlr_output_state *output_state) { struct wlr_color_transform *tr = NULL; if (gamma_control != NULL && gamma_control->table != NULL) { - const uint16_t *r = gamma_control->table; - const uint16_t *g = gamma_control->table + gamma_control->ramp_size; - const uint16_t *b = gamma_control->table + 2 * gamma_control->ramp_size; - - tr = wlr_color_transform_init_lut_3x1d(gamma_control->ramp_size, r, g, b); + tr = wlr_gamma_control_v1_get_color_transform(gamma_control); if (tr == NULL) { return false; } From 3e08e3be4a02122dd3b0bebe965ab0d410f08a01 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sun, 5 Oct 2025 21:22:07 +0200 Subject: [PATCH 154/311] gamma_control_v1: introduce fallback_gamma_size --- include/wlr/types/wlr_gamma_control_v1.h | 5 +++++ types/wlr_gamma_control_v1.c | 3 +++ 2 files changed, 8 insertions(+) diff --git a/include/wlr/types/wlr_gamma_control_v1.h b/include/wlr/types/wlr_gamma_control_v1.h index 4b0948964..7a6df98a9 100644 --- a/include/wlr/types/wlr_gamma_control_v1.h +++ b/include/wlr/types/wlr_gamma_control_v1.h @@ -10,6 +10,11 @@ struct wlr_gamma_control_manager_v1 { struct wl_global *global; struct wl_list controls; // wlr_gamma_control_v1.link + // Fallback to use when an struct wlr_output doesn't support gamma LUTs. + // Can be used to apply gamma LUTs via a struct wlr_renderer. Leave zero to + // indicate that the fallback is unsupported. + size_t fallback_gamma_size; + struct { struct wl_signal destroy; struct wl_signal set_gamma; // struct wlr_gamma_control_manager_v1_set_gamma_event diff --git a/types/wlr_gamma_control_v1.c b/types/wlr_gamma_control_v1.c index 04d73c2e5..42d14799d 100644 --- a/types/wlr_gamma_control_v1.c +++ b/types/wlr_gamma_control_v1.c @@ -157,6 +157,9 @@ static void gamma_control_manager_get_gamma_control(struct wl_client *client, } size_t gamma_size = wlr_output_get_gamma_size(output); + if (gamma_size == 0) { + gamma_size = manager->fallback_gamma_size; + } if (gamma_size == 0) { zwlr_gamma_control_v1_send_failed(resource); return; From 989cffe70d32db9fc2b5a776abd4767aabe430f6 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sun, 5 Oct 2025 21:21:32 +0200 Subject: [PATCH 155/311] scene: add software fallback for gamma LUT --- include/wlr/types/wlr_scene.h | 5 +++ types/scene/wlr_scene.c | 79 +++++++++++++++++++++++++++++++---- 2 files changed, 77 insertions(+), 7 deletions(-) diff --git a/include/wlr/types/wlr_scene.h b/include/wlr/types/wlr_scene.h index 58794e8db..9658a02b4 100644 --- a/include/wlr/types/wlr_scene.h +++ b/include/wlr/types/wlr_scene.h @@ -252,6 +252,11 @@ struct wlr_scene_output { bool gamma_lut_changed; struct wlr_gamma_control_v1 *gamma_lut; + struct wlr_color_transform *gamma_lut_color_transform; + + struct wlr_color_transform *prev_gamma_lut_color_transform; + struct wlr_color_transform *prev_supplied_color_transform; + struct wlr_color_transform *prev_combined_color_transform; struct wl_listener output_commit; struct wl_listener output_damage; diff --git a/types/scene/wlr_scene.c b/types/scene/wlr_scene.c index a06b641e3..c5cbcd29f 100644 --- a/types/scene/wlr_scene.c +++ b/types/scene/wlr_scene.c @@ -1530,6 +1530,8 @@ static void scene_handle_gamma_control_manager_v1_set_gamma(struct wl_listener * output->gamma_lut_changed = true; output->gamma_lut = event->control; + wlr_color_transform_unref(output->gamma_lut_color_transform); + output->gamma_lut_color_transform = wlr_gamma_control_v1_get_color_transform(event->control); wlr_output_schedule_frame(output->output); } @@ -1547,6 +1549,8 @@ static void scene_handle_gamma_control_manager_v1_destroy(struct wl_listener *li wl_list_for_each(output, &scene->outputs, link) { output->gamma_lut_changed = false; output->gamma_lut = NULL; + wlr_color_transform_unref(output->gamma_lut_color_transform); + output->gamma_lut_color_transform = NULL; } } @@ -1766,6 +1770,10 @@ void wlr_scene_output_destroy(struct wlr_scene_output *scene_output) { wl_list_remove(&scene_output->output_damage.link); wl_list_remove(&scene_output->output_needs_frame.link); wlr_drm_syncobj_timeline_unref(scene_output->in_timeline); + wlr_color_transform_unref(scene_output->gamma_lut_color_transform); + wlr_color_transform_unref(scene_output->prev_gamma_lut_color_transform); + wlr_color_transform_unref(scene_output->prev_supplied_color_transform); + wlr_color_transform_unref(scene_output->prev_combined_color_transform); wl_array_release(&scene_output->render_list); free(scene_output); } @@ -2104,16 +2112,15 @@ static void scene_output_state_attempt_gamma(struct wlr_scene_output *scene_outp return; } - if (!wlr_gamma_control_v1_apply(scene_output->gamma_lut, &gamma_pending)) { - wlr_output_state_finish(&gamma_pending); - return; - } - + wlr_output_state_set_color_transform(&gamma_pending, scene_output->gamma_lut_color_transform); scene_output->gamma_lut_changed = false; + if (!wlr_output_test_state(scene_output->output, &gamma_pending)) { wlr_gamma_control_v1_send_failed_and_destroy(scene_output->gamma_lut); scene_output->gamma_lut = NULL; + wlr_color_transform_unref(scene_output->gamma_lut_color_transform); + scene_output->gamma_lut_color_transform = NULL; wlr_output_state_finish(&gamma_pending); return; } @@ -2122,6 +2129,41 @@ static void scene_output_state_attempt_gamma(struct wlr_scene_output *scene_outp wlr_output_state_finish(&gamma_pending); } +static struct wlr_color_transform *scene_output_combine_color_transforms( + struct wlr_scene_output *scene_output, struct wlr_color_transform *supplied) { + struct wlr_color_transform *gamma_lut = scene_output->gamma_lut_color_transform; + assert(gamma_lut != NULL); + + if (gamma_lut == scene_output->prev_gamma_lut_color_transform && + supplied == scene_output->prev_supplied_color_transform) { + return wlr_color_transform_ref(scene_output->prev_combined_color_transform); + } + + struct wlr_color_transform *combined; + if (supplied == NULL) { + combined = wlr_color_transform_ref(gamma_lut); + } else { + struct wlr_color_transform *transforms[] = { + gamma_lut, + supplied, + }; + size_t transforms_len = sizeof(transforms) / sizeof(transforms[0]); + combined = wlr_color_transform_init_pipeline(transforms, transforms_len); + if (combined == NULL) { + return NULL; + } + } + + wlr_color_transform_unref(scene_output->prev_gamma_lut_color_transform); + scene_output->prev_gamma_lut_color_transform = wlr_color_transform_ref(gamma_lut); + wlr_color_transform_unref(scene_output->prev_supplied_color_transform); + scene_output->prev_supplied_color_transform = supplied ? wlr_color_transform_ref(supplied) : NULL; + wlr_color_transform_unref(scene_output->prev_combined_color_transform); + scene_output->prev_combined_color_transform = wlr_color_transform_ref(combined); + + return combined; +} + bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, struct wlr_output_state *state, const struct wlr_scene_output_state_options *options) { struct wlr_scene_output_state_options default_options = {0}; @@ -2145,6 +2187,16 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, enum wlr_scene_debug_damage_option debug_damage = scene_output->scene->debug_damage_option; + bool render_gamma_lut = false; + if (wlr_output_get_gamma_size(output) == 0 && output->renderer->features.output_color_transform) { + if (scene_output->gamma_lut_color_transform != scene_output->prev_gamma_lut_color_transform) { + scene_output_damage_whole(scene_output); + } + if (scene_output->gamma_lut_color_transform != NULL) { + render_gamma_lut = true; + } + } + struct render_data render_data = { .transform = output->transform, .scale = output->scale, @@ -2245,7 +2297,7 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, // - There are no color transforms that need to be applied // - Damage highlight debugging is not enabled enum scene_direct_scanout_result scanout_result = SCANOUT_INELIGIBLE; - if (options->color_transform == NULL && list_len == 1 + if (options->color_transform == NULL && !render_gamma_lut && list_len == 1 && debug_damage != WLR_SCENE_DEBUG_DAMAGE_HIGHLIGHT) { scanout_result = scene_entry_try_direct_scanout(&list_data[0], state, &render_data); } @@ -2319,6 +2371,17 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, color_transform = wlr_color_transform_ref(options->color_transform); } + if (render_gamma_lut) { + struct wlr_color_transform *combined = + scene_output_combine_color_transforms(scene_output, color_transform); + wlr_color_transform_unref(color_transform); + if (combined == NULL) { + wlr_buffer_unlock(buffer); + return false; + } + color_transform = combined; + } + scene_output->in_point++; struct wlr_render_pass *render_pass = wlr_renderer_begin_buffer_pass(output->renderer, buffer, &(struct wlr_buffer_pass_options){ @@ -2441,7 +2504,9 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, scene_output->in_point); } - scene_output_state_attempt_gamma(scene_output, state); + if (!render_gamma_lut) { + scene_output_state_attempt_gamma(scene_output, state); + } return true; } From 879243e370de6167d2c49510396f937b1a93fab5 Mon Sep 17 00:00:00 2001 From: David Turner Date: Mon, 20 Oct 2025 13:55:00 +0100 Subject: [PATCH 156/311] xwm: Fix double-close When an FD is passed to xcb_connect_to_fd(), xcb takes ownership of that FD and is responsible for closing it, which it does when xcb_disconnect() is called. But the xwayland handler code also keeps a copy of the FD and closes it via safe_close() in server_finish_process(). This double-close can cause all sorts of problems if another part of wlroots allocates another FD between the two closes - the latter close will close the wrong FD and things go horribly wrong (in my case leading to use-after-free and segfaults). Fix this by setting wm_fd[0]=-1 after calling xwm_create(), and ensuring that xwm_create() closes the FD if startup errors occur. --- include/xwayland/xwm.h | 1 + xwayland/xwayland.c | 3 +++ xwayland/xwm.c | 3 +++ 3 files changed, 7 insertions(+) diff --git a/include/xwayland/xwm.h b/include/xwayland/xwm.h index e460bbb63..73f440d29 100644 --- a/include/xwayland/xwm.h +++ b/include/xwayland/xwm.h @@ -164,6 +164,7 @@ struct wlr_xwm { struct wl_listener drop_focus_destroy; }; +// xwm_create takes ownership of wm_fd and will close it under all circumstances. struct wlr_xwm *xwm_create(struct wlr_xwayland *wlr_xwayland, int wm_fd); void xwm_destroy(struct wlr_xwm *xwm); diff --git a/xwayland/xwayland.c b/xwayland/xwayland.c index 5d51df074..3aa47bac2 100644 --- a/xwayland/xwayland.c +++ b/xwayland/xwayland.c @@ -42,6 +42,9 @@ static void handle_server_start(struct wl_listener *listener, void *data) { static void xwayland_mark_ready(struct wlr_xwayland *xwayland) { assert(xwayland->server->wm_fd[0] >= 0); xwayland->xwm = xwm_create(xwayland, xwayland->server->wm_fd[0]); + // xwm_create takes ownership of wm_fd[0] under all circumstances + xwayland->server->wm_fd[0] = -1; + if (!xwayland->xwm) { return; } diff --git a/xwayland/xwm.c b/xwayland/xwm.c index a82e8b145..2bb4e4c64 100644 --- a/xwayland/xwm.c +++ b/xwayland/xwm.c @@ -2530,6 +2530,7 @@ void xwm_set_cursor(struct wlr_xwm *xwm, const uint8_t *pixels, uint32_t stride, struct wlr_xwm *xwm_create(struct wlr_xwayland *xwayland, int wm_fd) { struct wlr_xwm *xwm = calloc(1, sizeof(*xwm)); if (xwm == NULL) { + close(wm_fd); return NULL; } @@ -2544,11 +2545,13 @@ struct wlr_xwm *xwm_create(struct wlr_xwayland *xwayland, int wm_fd) { xwm->ping_timeout = 10000; + // xcb_connect_to_fd takes ownership of the FD regardless of success/failure xwm->xcb_conn = xcb_connect_to_fd(wm_fd, NULL); int rc = xcb_connection_has_error(xwm->xcb_conn); if (rc) { wlr_log(WLR_ERROR, "xcb connect failed: %d", rc); + xcb_disconnect(xwm->xcb_conn); free(xwm); return NULL; } From 604fcdb1db36d8bad0ada101d17c2d45e7510017 Mon Sep 17 00:00:00 2001 From: llyyr Date: Mon, 6 Oct 2025 08:10:49 +0530 Subject: [PATCH 157/311] render/vulkan: clip negative values before applying transfer function Not all eotf or eotf inverse are well defined for values outside the intended domain, so just ignore it and clamp it away. An alternative solution would be to use sign preserving pow here (i.e. sign(x) * pow(abs(x), p)), however I'm not sure that makes sense or is defined anywhere. Negative values here are likely a result of colors being outside the gamut range, so clipping them to 0 is more correct than mirroring from 0. --- render/vulkan/shaders/output.frag | 3 +++ render/vulkan/shaders/texture.frag | 3 +++ 2 files changed, 6 insertions(+) diff --git a/render/vulkan/shaders/output.frag b/render/vulkan/shaders/output.frag index b9cfcaaa2..5b952fcff 100644 --- a/render/vulkan/shaders/output.frag +++ b/render/vulkan/shaders/output.frag @@ -72,6 +72,9 @@ void main() { rgb = mat3(data.matrix) * rgb; + if (OUTPUT_TRANSFORM != OUTPUT_TRANSFORM_IDENTITY) { + rgb = max(rgb, vec3(0)); + } if (OUTPUT_TRANSFORM == OUTPUT_TRANSFORM_LUT_3D) { // Apply 3D LUT vec3 pos = data.lut_3d_offset + rgb * data.lut_3d_scale; diff --git a/render/vulkan/shaders/texture.frag b/render/vulkan/shaders/texture.frag index b7b78b19a..57d2d049c 100644 --- a/render/vulkan/shaders/texture.frag +++ b/render/vulkan/shaders/texture.frag @@ -66,6 +66,9 @@ void main() { rgb = in_color.rgb / alpha; } + if (TEXTURE_TRANSFORM != TEXTURE_TRANSFORM_IDENTITY) { + rgb = max(rgb, vec3(0)); + } if (TEXTURE_TRANSFORM == TEXTURE_TRANSFORM_SRGB) { rgb = srgb_color_to_linear(rgb); } else if (TEXTURE_TRANSFORM == TEXTURE_TRANSFORM_ST2084_PQ) { From 47d0a90274be1c1ab3cb75fe741c9e2a98e781b4 Mon Sep 17 00:00:00 2001 From: xurui Date: Tue, 4 Nov 2025 17:21:53 +0800 Subject: [PATCH 158/311] types/wlr_input_device: name maybe NULL Signed-off-by: xurui --- types/wlr_input_device.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/wlr_input_device.c b/types/wlr_input_device.c index 8d6301589..201059db8 100644 --- a/types/wlr_input_device.c +++ b/types/wlr_input_device.c @@ -7,7 +7,7 @@ void wlr_input_device_init(struct wlr_input_device *dev, enum wlr_input_device_type type, const char *name) { *dev = (struct wlr_input_device){ .type = type, - .name = strdup(name), + .name = name ? strdup(name) : NULL, }; wl_signal_init(&dev->events.destroy); From 8f7d763ad1bd76e659d96e12374a507368eed4cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Poisot?= Date: Tue, 28 Oct 2025 14:58:37 +0000 Subject: [PATCH 159/311] drm: save edid color characteristics in wlr_output --- backend/drm/util.c | 11 +++++++++++ include/wlr/types/wlr_output.h | 2 ++ 2 files changed, 13 insertions(+) diff --git a/backend/drm/util.c b/backend/drm/util.c index dd2b37351..1e0307dcb 100644 --- a/backend/drm/util.c +++ b/backend/drm/util.c @@ -83,6 +83,17 @@ void parse_edid(struct wlr_drm_connector *conn, size_t len, const uint8_t *data) output->model = di_info_get_model(info); output->serial = di_info_get_serial(info); + const struct di_color_primaries *color_characteristics = di_info_get_default_color_primaries(info); + if (color_characteristics->has_primaries) { + output->default_primaries_value = (struct wlr_color_primaries) { + .red = { .x = color_characteristics->primary[0].x, .y = color_characteristics->primary[0].y }, + .green = { .x = color_characteristics->primary[1].x, .y = color_characteristics->primary[1].y }, + .blue = { .x = color_characteristics->primary[2].x, .y = color_characteristics->primary[2].y }, + .white = { .x = color_characteristics->default_white.x, .y = color_characteristics->default_white.y }, + }; + output->default_primaries = &output->default_primaries_value; + } + const struct di_supported_signal_colorimetry *colorimetry = di_info_get_supported_signal_colorimetry(info); bool has_bt2020 = colorimetry->bt2020_cycc || colorimetry->bt2020_ycc || colorimetry->bt2020_rgb; if (conn->props.colorspace != 0 && has_bt2020) { diff --git a/include/wlr/types/wlr_output.h b/include/wlr/types/wlr_output.h index 2ae11a4d3..3cf8778a5 100644 --- a/include/wlr/types/wlr_output.h +++ b/include/wlr/types/wlr_output.h @@ -187,6 +187,7 @@ struct wlr_output { char *description; // may be NULL char *make, *model, *serial; // may be NULL int32_t phys_width, phys_height; // mm + const struct wlr_color_primaries *default_primaries; // may be NULL // Note: some backends may have zero modes struct wl_list modes; // wlr_output_mode.link @@ -268,6 +269,7 @@ struct wlr_output { struct wl_listener display_destroy; struct wlr_output_image_description image_description_value; struct wlr_color_transform *color_transform; + struct wlr_color_primaries default_primaries_value; } WLR_PRIVATE; }; From aef84f0e4d506dda18992f0213e3233a4ed2ac20 Mon Sep 17 00:00:00 2001 From: David96 Date: Sun, 2 Feb 2025 20:47:56 +0100 Subject: [PATCH 160/311] wlr_virtual_pointer: Set axis source on all axis Currently it is possible to crash a wlroots compositor by setting any axis source other than 0 and sending an axis event in the HORIZONTAL direction from wlr_virtual_pointer since the axis source is only set on the first axis. This then hits the assert in wlr_seat_pointer.c:332. Fix by always setting the source on all axis. --- types/wlr_virtual_pointer_v1.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/types/wlr_virtual_pointer_v1.c b/types/wlr_virtual_pointer_v1.c index b867c8fb1..fed5790a9 100644 --- a/types/wlr_virtual_pointer_v1.c +++ b/types/wlr_virtual_pointer_v1.c @@ -134,8 +134,11 @@ static void virtual_pointer_axis_source(struct wl_client *client, if (pointer == NULL) { return; } - pointer->axis_event[pointer->axis].pointer = &pointer->pointer; - pointer->axis_event[pointer->axis].source = source; + int n_axis = sizeof(pointer->axis_event) / sizeof(pointer->axis_event[0]); + for (int i = 0; i < n_axis; i++) { + pointer->axis_event[i].pointer = &pointer->pointer; + pointer->axis_event[i].source = source; + } } static void virtual_pointer_axis_stop(struct wl_client *client, From 0d24cdb8220757c8515702210bfc4d0c76c4f758 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Poisot?= Date: Mon, 10 Nov 2025 16:03:07 +0000 Subject: [PATCH 161/311] cursor: apply output image description when preparing texture --- types/output/cursor.c | 13 ++++++++++++- types/wlr_cursor.c | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/types/output/cursor.c b/types/output/cursor.c index 70647afb7..a24f4abd1 100644 --- a/types/output/cursor.c +++ b/types/output/cursor.c @@ -10,6 +10,7 @@ #include #include #include +#include "render/color.h" #include "types/wlr_buffer.h" #include "types/wlr_output.h" @@ -255,7 +256,17 @@ static struct wlr_buffer *render_cursor_buffer(struct wlr_output_cursor *cursor) wlr_box_transform(&dst_box, &dst_box, wlr_output_transform_invert(output->transform), buffer->width, buffer->height); - struct wlr_render_pass *pass = wlr_renderer_begin_buffer_pass(renderer, buffer, NULL); + struct wlr_buffer_pass_options options = {0}; + struct wlr_color_primaries primaries_value; + if (output->image_description != NULL) { + options.color_transform = wlr_color_transform_init_linear_to_inverse_eotf( + output->image_description->transfer_function); + wlr_color_primaries_from_named(&primaries_value, + output->image_description->primaries); + options.primaries = &primaries_value; + } + struct wlr_render_pass *pass = wlr_renderer_begin_buffer_pass(renderer, buffer, &options); + wlr_color_transform_unref(options.color_transform); if (pass == NULL) { wlr_buffer_unlock(buffer); return NULL; diff --git a/types/wlr_cursor.c b/types/wlr_cursor.c index 5f4aac398..7895cc4ed 100644 --- a/types/wlr_cursor.c +++ b/types/wlr_cursor.c @@ -640,7 +640,7 @@ static void output_cursor_output_handle_output_commit( const struct wlr_output_event_commit *event = data; if (event->state->committed & (WLR_OUTPUT_STATE_SCALE | WLR_OUTPUT_STATE_TRANSFORM - | WLR_OUTPUT_STATE_ENABLED)) { + | WLR_OUTPUT_STATE_ENABLED | WLR_OUTPUT_STATE_IMAGE_DESCRIPTION)) { cursor_output_cursor_update(output_cursor); } From 378a4918238f1fc12bb4e1062d22b5b884768293 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Tue, 28 Oct 2025 20:40:09 +0100 Subject: [PATCH 162/311] render/color: turn enum wlr_color_encoding into a bitfield This will allow us to describe the set of supported encodings as a bitfield. --- include/wlr/render/color.h | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/include/wlr/render/color.h b/include/wlr/render/color.h index bc3baf181..b97e4c91d 100644 --- a/include/wlr/render/color.h +++ b/include/wlr/render/color.h @@ -48,15 +48,15 @@ enum wlr_alpha_mode { * value is unset or unknown. */ enum wlr_color_encoding { - WLR_COLOR_ENCODING_NONE, - WLR_COLOR_ENCODING_IDENTITY, - WLR_COLOR_ENCODING_BT709, - WLR_COLOR_ENCODING_FCC, - WLR_COLOR_ENCODING_BT601, - WLR_COLOR_ENCODING_SMPTE240, - WLR_COLOR_ENCODING_BT2020, - WLR_COLOR_ENCODING_BT2020_CL, - WLR_COLOR_ENCODING_ICTCP, + WLR_COLOR_ENCODING_NONE = 0, + WLR_COLOR_ENCODING_IDENTITY = 1 << 0, + WLR_COLOR_ENCODING_BT709 = 1 << 1, + WLR_COLOR_ENCODING_FCC = 1 << 2, + WLR_COLOR_ENCODING_BT601 = 1 << 3, + WLR_COLOR_ENCODING_SMPTE240 = 1 << 4, + WLR_COLOR_ENCODING_BT2020 = 1 << 5, + WLR_COLOR_ENCODING_BT2020_CL = 1 << 6, + WLR_COLOR_ENCODING_ICTCP = 1 << 7, }; /** From 2a87ec8a356ee6e40d69530b88d8c05c900a9da8 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Tue, 28 Oct 2025 20:44:00 +0100 Subject: [PATCH 163/311] render: add color encoding and range to wlr_render_texture_options --- include/wlr/render/pass.h | 4 ++++ include/wlr/render/wlr_renderer.h | 2 ++ 2 files changed, 6 insertions(+) diff --git a/include/wlr/render/pass.h b/include/wlr/render/pass.h index 1785ee562..9cce7c0a8 100644 --- a/include/wlr/render/pass.h +++ b/include/wlr/render/pass.h @@ -107,6 +107,10 @@ struct wlr_render_texture_options { enum wlr_color_transfer_function transfer_function; /* Primaries describing the color volume of the source texture */ const struct wlr_color_primaries *primaries; + /* Color encoding of the source texture for YCbCr conversion to RGB */ + enum wlr_color_encoding color_encoding; + /* Color range of the source texture */ + enum wlr_color_range color_range; /* Wait for a timeline synchronization point before texturing. * diff --git a/include/wlr/render/wlr_renderer.h b/include/wlr/render/wlr_renderer.h index 62a1cca2d..902d7564d 100644 --- a/include/wlr/render/wlr_renderer.h +++ b/include/wlr/render/wlr_renderer.h @@ -29,6 +29,8 @@ struct wlr_renderer { // Capabilities required for the buffer used as a render target (bitmask of // enum wlr_buffer_cap) uint32_t render_buffer_caps; + // Supported color encodings for YCbCr textures + uint32_t color_encodings; // bitmask of enum wlr_color_encoding struct { struct wl_signal destroy; From fecaf6d1e88654fc6cbabb332d5b5ab098fdb8e6 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Tue, 28 Oct 2025 20:44:48 +0100 Subject: [PATCH 164/311] render/vulkan: add suport for color encoding and range --- include/render/vulkan.h | 8 ++++- render/vulkan/pass.c | 18 ++++++++-- render/vulkan/renderer.c | 73 ++++++++++++++++++++++++++-------------- 3 files changed, 70 insertions(+), 29 deletions(-) diff --git a/include/render/vulkan.h b/include/render/vulkan.h index 5358b01c1..3d1b33ba6 100644 --- a/include/render/vulkan.h +++ b/include/render/vulkan.h @@ -127,8 +127,14 @@ const struct wlr_vk_format_modifier_props *vulkan_format_props_find_modifier( void vulkan_format_props_finish(struct wlr_vk_format_props *props); struct wlr_vk_pipeline_layout_key { - const struct wlr_vk_format *ycbcr_format; enum wlr_scale_filter_mode filter_mode; + + // for YCbCr pipelines only + struct { + const struct wlr_vk_format *format; + enum wlr_color_encoding encoding; + enum wlr_color_range range; + } ycbcr; }; struct wlr_vk_pipeline_layout { diff --git a/render/vulkan/pass.c b/render/vulkan/pass.c index 398ee2104..c69d48e6c 100644 --- a/render/vulkan/pass.c +++ b/render/vulkan/pass.c @@ -681,7 +681,7 @@ static void render_pass_add_rect(struct wlr_render_pass *wlr_pass, pass->render_setup, &(struct wlr_vk_pipeline_key) { .source = WLR_VK_SHADER_SOURCE_SINGLE_COLOR, - .layout = { .ycbcr_format = NULL }, + .layout = {0}, }); if (!pipe) { pass->failed = true; @@ -807,12 +807,26 @@ static void render_pass_add_texture(struct wlr_render_pass *wlr_pass, break; } + enum wlr_color_encoding color_encoding = options->color_encoding; + if (texture->format->is_ycbcr && color_encoding == WLR_COLOR_ENCODING_NONE) { + color_encoding = WLR_COLOR_ENCODING_BT601; + } + + enum wlr_color_range color_range = options->color_range; + if (texture->format->is_ycbcr && color_range == WLR_COLOR_RANGE_NONE) { + color_range = WLR_COLOR_RANGE_LIMITED; + } + struct wlr_vk_pipeline *pipe = setup_get_or_create_pipeline( pass->render_setup, &(struct wlr_vk_pipeline_key) { .source = WLR_VK_SHADER_SOURCE_TEXTURE, .layout = { - .ycbcr_format = texture->format->is_ycbcr ? texture->format : NULL, + .ycbcr = { + .format = texture->format->is_ycbcr ? texture->format : NULL, + .encoding = color_encoding, + .range = color_range, + }, .filter_mode = options->filter_mode, }, .texture_transform = tex_transform, diff --git a/render/vulkan/renderer.c b/render/vulkan/renderer.c index 3f880ca6c..2f5aeb331 100644 --- a/render/vulkan/renderer.c +++ b/render/vulkan/renderer.c @@ -1630,14 +1630,16 @@ static bool init_blend_to_output_layouts(struct wlr_vk_renderer *renderer) { static bool pipeline_layout_key_equals( const struct wlr_vk_pipeline_layout_key *a, const struct wlr_vk_pipeline_layout_key *b) { - assert(!a->ycbcr_format || a->ycbcr_format->is_ycbcr); - assert(!b->ycbcr_format || b->ycbcr_format->is_ycbcr); + assert(!a->ycbcr.format || a->ycbcr.format->is_ycbcr); + assert(!b->ycbcr.format || b->ycbcr.format->is_ycbcr); if (a->filter_mode != b->filter_mode) { return false; } - if (a->ycbcr_format != b->ycbcr_format) { + if (a->ycbcr.format != b->ycbcr.format || + a->ycbcr.encoding != b->ycbcr.encoding || + a->ycbcr.range != b->ycbcr.range) { return false; } @@ -1945,6 +1947,33 @@ static bool init_blend_to_output_pipeline(struct wlr_vk_renderer *renderer, return true; } +static VkSamplerYcbcrModelConversion ycbcr_model_from_wlr(enum wlr_color_encoding encoding) { + switch (encoding) { + case WLR_COLOR_ENCODING_NONE: + abort(); // must be explicit + case WLR_COLOR_ENCODING_BT601: + return VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_601; + case WLR_COLOR_ENCODING_BT709: + return VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_709; + case WLR_COLOR_ENCODING_BT2020: + return VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_2020; + default: + abort(); // unsupported + } +} + +static VkSamplerYcbcrRange ycbcr_range_from_wlr(enum wlr_color_range range) { + switch (range) { + case WLR_COLOR_RANGE_NONE: + abort(); // must be explicit + case WLR_COLOR_RANGE_LIMITED: + return VK_SAMPLER_YCBCR_RANGE_ITU_NARROW; + case WLR_COLOR_RANGE_FULL: + return VK_SAMPLER_YCBCR_RANGE_ITU_FULL; + } + abort(); // unreachable +} + struct wlr_vk_pipeline_layout *get_or_create_pipeline_layout( struct wlr_vk_renderer *renderer, const struct wlr_vk_pipeline_layout_key *key) { @@ -1986,12 +2015,12 @@ struct wlr_vk_pipeline_layout *get_or_create_pipeline_layout( .maxLod = 0.25f, }; - if (key->ycbcr_format) { + if (key->ycbcr.format) { VkSamplerYcbcrConversionCreateInfo conversion_create_info = { .sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_CREATE_INFO, - .format = key->ycbcr_format->vk, - .ycbcrModel = VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_601, - .ycbcrRange = VK_SAMPLER_YCBCR_RANGE_ITU_NARROW, + .format = key->ycbcr.format->vk, + .ycbcrModel = ycbcr_model_from_wlr(key->ycbcr.encoding), + .ycbcrRange = ycbcr_range_from_wlr(key->ycbcr.range), .xChromaOffset = VK_CHROMA_LOCATION_MIDPOINT, .yChromaOffset = VK_CHROMA_LOCATION_MIDPOINT, .chromaFilter = VK_FILTER_LINEAR, @@ -2009,6 +2038,9 @@ struct wlr_vk_pipeline_layout *get_or_create_pipeline_layout( .conversion = pipeline_layout->ycbcr.conversion, }; sampler_create_info.pNext = &conversion_info; + } else { + assert(key->ycbcr.encoding == WLR_COLOR_ENCODING_NONE); + assert(key->ycbcr.range == WLR_COLOR_RANGE_NONE); } res = vkCreateSampler(renderer->dev->dev, &sampler_create_info, NULL, &pipeline_layout->sampler); @@ -2429,7 +2461,7 @@ static struct wlr_vk_render_format_setup *find_or_create_render_setup( if (!setup_get_or_create_pipeline(setup, &(struct wlr_vk_pipeline_key){ .source = WLR_VK_SHADER_SOURCE_SINGLE_COLOR, - .layout = { .ycbcr_format = NULL }, + .layout = {0}, })) { goto error; } @@ -2437,7 +2469,7 @@ static struct wlr_vk_render_format_setup *find_or_create_render_setup( if (!setup_get_or_create_pipeline(setup, &(struct wlr_vk_pipeline_key){ .source = WLR_VK_SHADER_SOURCE_TEXTURE, .texture_transform = WLR_VK_TEXTURE_TRANSFORM_IDENTITY, - .layout = {.ycbcr_format = NULL }, + .layout = {0}, })) { goto error; } @@ -2445,27 +2477,11 @@ static struct wlr_vk_render_format_setup *find_or_create_render_setup( if (!setup_get_or_create_pipeline(setup, &(struct wlr_vk_pipeline_key){ .source = WLR_VK_SHADER_SOURCE_TEXTURE, .texture_transform = WLR_VK_TEXTURE_TRANSFORM_SRGB, - .layout = {.ycbcr_format = NULL }, + .layout = {0}, })) { goto error; } - for (size_t i = 0; i < renderer->dev->format_prop_count; i++) { - const struct wlr_vk_format *format = &renderer->dev->format_props[i].format; - const struct wlr_vk_pipeline_layout_key layout = { - .ycbcr_format = format, - }; - - if (format->is_ycbcr) { - if (!setup_get_or_create_pipeline(setup, &(struct wlr_vk_pipeline_key){ - .texture_transform = WLR_VK_TEXTURE_TRANSFORM_SRGB, - .layout = layout - })) { - goto error; - } - } - } - wl_list_insert(&renderer->render_format_setups, &setup->link); return setup; @@ -2496,6 +2512,11 @@ struct wlr_renderer *vulkan_renderer_create_for_device(struct wlr_vk_device *dev wl_list_init(&renderer->color_transforms); wl_list_init(&renderer->pipeline_layouts); + renderer->wlr_renderer.color_encodings = + WLR_COLOR_ENCODING_BT601 | + WLR_COLOR_ENCODING_BT709 | + WLR_COLOR_ENCODING_BT2020; + uint64_t cap_syncobj_timeline; if (dev->drm_fd >= 0 && drmGetCap(dev->drm_fd, DRM_CAP_SYNCOBJ_TIMELINE, &cap_syncobj_timeline) == 0) { renderer->wlr_renderer.features.timeline = dev->sync_file_import_export && cap_syncobj_timeline != 0; From e8f9e9eea6ac64f294254063ce56e3f84301df76 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Tue, 28 Oct 2025 20:45:11 +0100 Subject: [PATCH 165/311] color_representation_v1: make supported_alpha_modes const --- include/wlr/types/wlr_color_representation_v1.h | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/include/wlr/types/wlr_color_representation_v1.h b/include/wlr/types/wlr_color_representation_v1.h index 0a1958dec..7a21babda 100644 --- a/include/wlr/types/wlr_color_representation_v1.h +++ b/include/wlr/types/wlr_color_representation_v1.h @@ -44,12 +44,10 @@ struct wlr_color_representation_manager_v1 { // Options used when initialising a wlr_color_representation_manager_v1 struct wlr_color_representation_v1_options { - enum wp_color_representation_surface_v1_alpha_mode - *supported_alpha_modes; + const enum wp_color_representation_surface_v1_alpha_mode *supported_alpha_modes; size_t supported_alpha_modes_len; - const struct wlr_color_representation_v1_coeffs_and_range - *supported_coeffs_and_ranges; + const struct wlr_color_representation_v1_coeffs_and_range *supported_coeffs_and_ranges; size_t supported_coeffs_and_ranges_len; }; From 2fbc0b5ac1d9877d024541eb4779a77e1f9102b1 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Tue, 28 Oct 2025 20:45:41 +0100 Subject: [PATCH 166/311] scene: add support for color encoding and range --- include/wlr/types/wlr_scene.h | 8 ++++++++ types/scene/wlr_scene.c | 27 +++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/include/wlr/types/wlr_scene.h b/include/wlr/types/wlr_scene.h index 9658a02b4..83e5ad34a 100644 --- a/include/wlr/types/wlr_scene.h +++ b/include/wlr/types/wlr_scene.h @@ -197,6 +197,8 @@ struct wlr_scene_buffer { pixman_region32_t opaque_region; enum wlr_color_transfer_function transfer_function; enum wlr_color_named_primaries primaries; + enum wlr_color_encoding color_encoding; + enum wlr_color_range color_range; struct { uint64_t active_outputs; @@ -564,6 +566,12 @@ void wlr_scene_buffer_set_transfer_function(struct wlr_scene_buffer *scene_buffe void wlr_scene_buffer_set_primaries(struct wlr_scene_buffer *scene_buffer, enum wlr_color_named_primaries primaries); +void wlr_scene_buffer_set_color_encoding(struct wlr_scene_buffer *scene_buffer, + enum wlr_color_encoding encoding); + +void wlr_scene_buffer_set_color_range(struct wlr_scene_buffer *scene_buffer, + enum wlr_color_range range); + /** * Calls the buffer's frame_done signal. */ diff --git a/types/scene/wlr_scene.c b/types/scene/wlr_scene.c index c5cbcd29f..0c854c5b7 100644 --- a/types/scene/wlr_scene.c +++ b/types/scene/wlr_scene.c @@ -1121,6 +1121,26 @@ void wlr_scene_buffer_set_primaries(struct wlr_scene_buffer *scene_buffer, scene_node_update(&scene_buffer->node, NULL); } +void wlr_scene_buffer_set_color_encoding(struct wlr_scene_buffer *scene_buffer, + enum wlr_color_encoding color_encoding) { + if (scene_buffer->color_encoding == color_encoding) { + return; + } + + scene_buffer->color_encoding = color_encoding; + scene_node_update(&scene_buffer->node, NULL); +} + +void wlr_scene_buffer_set_color_range(struct wlr_scene_buffer *scene_buffer, + enum wlr_color_range color_range) { + if (scene_buffer->color_range == color_range) { + return; + } + + scene_buffer->color_range = color_range; + scene_node_update(&scene_buffer->node, NULL); +} + static struct wlr_texture *scene_buffer_get_texture( struct wlr_scene_buffer *scene_buffer, struct wlr_renderer *renderer) { if (scene_buffer->buffer == NULL || scene_buffer->texture != NULL) { @@ -1475,6 +1495,8 @@ static void scene_entry_render(struct render_list_entry *entry, const struct ren WLR_RENDER_BLEND_MODE_PREMULTIPLIED : WLR_RENDER_BLEND_MODE_NONE, .transfer_function = scene_buffer->transfer_function, .primaries = scene_buffer->primaries != 0 ? &primaries : NULL, + .color_encoding = scene_buffer->color_encoding, + .color_range = scene_buffer->color_range, .wait_timeline = scene_buffer->wait_timeline, .wait_point = scene_buffer->wait_point, }); @@ -2015,6 +2037,11 @@ static enum scene_direct_scanout_result scene_entry_try_direct_scanout( return SCANOUT_INELIGIBLE; } + if (buffer->color_encoding != WLR_COLOR_ENCODING_NONE || + buffer->color_range != WLR_COLOR_RANGE_NONE) { + return SCANOUT_INELIGIBLE; + } + // We want to ensure optimal buffer selection, but as direct-scanout can be enabled and disabled // on a frame-by-frame basis, we wait for a few frames to send the new format recommendations. // Maybe we should only send feedback in this case if tests fail. From 87c3cb5c529f9180c16a9984225b00481c5c33cd Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Tue, 28 Oct 2025 20:46:03 +0100 Subject: [PATCH 167/311] scene: add support for color-representation-v1 coeffs and range --- types/scene/surface.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/types/scene/surface.c b/types/scene/surface.c index 68a445b98..0c24208cb 100644 --- a/types/scene/surface.c +++ b/types/scene/surface.c @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -259,6 +260,19 @@ static void surface_reconfigure(struct wlr_scene_surface *scene_surface) { primaries = wlr_color_manager_v1_primaries_to_wlr(img_desc->primaries_named); } + enum wlr_color_encoding color_encoding = WLR_COLOR_ENCODING_NONE; + enum wlr_color_range color_range = WLR_COLOR_RANGE_NONE; + const struct wlr_color_representation_v1_surface_state *color_repr = + wlr_color_representation_v1_get_surface_state(surface); + if (color_repr != NULL) { + if (color_repr->coefficients != 0) { + color_encoding = wlr_color_representation_v1_color_encoding_to_wlr(color_repr->coefficients); + } + if (color_repr->range != 0) { + color_range = wlr_color_representation_v1_color_range_to_wlr(color_repr->range); + } + } + wlr_scene_buffer_set_opaque_region(scene_buffer, &opaque); wlr_scene_buffer_set_source_box(scene_buffer, &src_box); wlr_scene_buffer_set_dest_size(scene_buffer, width, height); @@ -266,6 +280,8 @@ static void surface_reconfigure(struct wlr_scene_surface *scene_surface) { wlr_scene_buffer_set_opacity(scene_buffer, opacity); wlr_scene_buffer_set_transfer_function(scene_buffer, tf); wlr_scene_buffer_set_primaries(scene_buffer, primaries); + wlr_scene_buffer_set_color_encoding(scene_buffer, color_encoding); + wlr_scene_buffer_set_color_range(scene_buffer, color_range); scene_buffer_unmark_client_buffer(scene_buffer); From fa7b66bcf479777e50c5c67eba839fd55192b502 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Tue, 28 Oct 2025 21:09:20 +0100 Subject: [PATCH 168/311] color_representation_v1: add helper to create global from renderer --- .../wlr/types/wlr_color_representation_v1.h | 4 ++ types/wlr_color_representation_v1.c | 53 +++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/include/wlr/types/wlr_color_representation_v1.h b/include/wlr/types/wlr_color_representation_v1.h index 7a21babda..ed6afa007 100644 --- a/include/wlr/types/wlr_color_representation_v1.h +++ b/include/wlr/types/wlr_color_representation_v1.h @@ -14,6 +14,7 @@ #include struct wlr_surface; +struct wlr_renderer; // Supported coefficients and range are always paired together struct wlr_color_representation_v1_coeffs_and_range { @@ -55,6 +56,9 @@ struct wlr_color_representation_manager_v1 *wlr_color_representation_manager_v1_ struct wl_display *display, uint32_t version, const struct wlr_color_representation_v1_options *options); +struct wlr_color_representation_manager_v1 *wlr_color_representation_manager_v1_create_with_renderer( + struct wl_display *display, uint32_t version, struct wlr_renderer *renderer); + // This is all the color-representation state which can be attached to a // surface, double-buffered and made current on commit struct wlr_color_representation_v1_surface_state { diff --git a/types/wlr_color_representation_v1.c b/types/wlr_color_representation_v1.c index 6590ec4e7..af7fac60c 100644 --- a/types/wlr_color_representation_v1.c +++ b/types/wlr_color_representation_v1.c @@ -1,6 +1,7 @@ #include #include +#include #include #include #include @@ -400,6 +401,58 @@ err_options: return NULL; } +static const enum wp_color_representation_surface_v1_coefficients coefficients[] = { + WP_COLOR_REPRESENTATION_SURFACE_V1_COEFFICIENTS_IDENTITY, + WP_COLOR_REPRESENTATION_SURFACE_V1_COEFFICIENTS_BT709, + WP_COLOR_REPRESENTATION_SURFACE_V1_COEFFICIENTS_FCC, + WP_COLOR_REPRESENTATION_SURFACE_V1_COEFFICIENTS_BT601, + WP_COLOR_REPRESENTATION_SURFACE_V1_COEFFICIENTS_SMPTE240, + WP_COLOR_REPRESENTATION_SURFACE_V1_COEFFICIENTS_BT2020, + WP_COLOR_REPRESENTATION_SURFACE_V1_COEFFICIENTS_BT2020_CL, + WP_COLOR_REPRESENTATION_SURFACE_V1_COEFFICIENTS_ICTCP, +}; + +#define COEFFICIENTS_LEN (sizeof(coefficients) / sizeof(coefficients[0])) + +static const enum wp_color_representation_surface_v1_range ranges[] = { + WP_COLOR_REPRESENTATION_SURFACE_V1_RANGE_LIMITED, + WP_COLOR_REPRESENTATION_SURFACE_V1_RANGE_FULL, +}; + +#define RANGES_LEN (sizeof(ranges) / sizeof(ranges[0])) + +struct wlr_color_representation_manager_v1 *wlr_color_representation_manager_v1_create_with_renderer( + struct wl_display *display, uint32_t version, struct wlr_renderer *renderer) { + const enum wp_color_representation_surface_v1_alpha_mode alpha_modes[] = { + WP_COLOR_REPRESENTATION_SURFACE_V1_ALPHA_MODE_PREMULTIPLIED_ELECTRICAL, + }; + + struct wlr_color_representation_v1_coeffs_and_range coeffs_and_ranges[COEFFICIENTS_LEN * RANGES_LEN]; + size_t coeffs_and_ranges_len = 0; + for (size_t i = 0; i < COEFFICIENTS_LEN; i++) { + enum wp_color_representation_surface_v1_coefficients coeffs = coefficients[i]; + enum wlr_color_encoding enc = wlr_color_representation_v1_color_encoding_to_wlr(coeffs); + if (!(renderer->color_encodings & enc)) { + continue; + } + for (size_t j = 0; j < RANGES_LEN; j++) { + coeffs_and_ranges[coeffs_and_ranges_len] = (struct wlr_color_representation_v1_coeffs_and_range){ + .coeffs = coeffs, + .range = ranges[j], + }; + coeffs_and_ranges_len++; + } + } + + const struct wlr_color_representation_v1_options options = { + .supported_alpha_modes = alpha_modes, + .supported_alpha_modes_len = sizeof(alpha_modes) / sizeof(alpha_modes[0]), + .supported_coeffs_and_ranges = coeffs_and_ranges, + .supported_coeffs_and_ranges_len = coeffs_and_ranges_len, + }; + return wlr_color_representation_manager_v1_create(display, version, &options); +} + const struct wlr_color_representation_v1_surface_state *wlr_color_representation_v1_get_surface_state( struct wlr_surface *surface) { struct wlr_color_representation_v1 *color_repr = color_repr_from_surface(surface); From 106f0f950639924d706df21f48545b09db8197ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Poisot?= Date: Fri, 14 Nov 2025 11:10:37 +0000 Subject: [PATCH 169/311] output/state: add missing unref for color_transform --- types/output/state.c | 1 + 1 file changed, 1 insertion(+) diff --git a/types/output/state.c b/types/output/state.c index a2e8a0042..f2c655e21 100644 --- a/types/output/state.c +++ b/types/output/state.c @@ -19,6 +19,7 @@ void wlr_output_state_finish(struct wlr_output_state *state) { pixman_region32_fini(&state->damage); wlr_drm_syncobj_timeline_unref(state->wait_timeline); wlr_drm_syncobj_timeline_unref(state->signal_timeline); + wlr_color_transform_unref(state->color_transform); free(state->image_description); } From b98904705d182fd3fd37348df989827e59e73f3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Poisot?= Date: Sat, 22 Nov 2025 15:45:00 +0000 Subject: [PATCH 170/311] render/vulkan: fix single-pass linear path Fixes d1c88e9 "render/vulkan: add linear single-subpass" `find_or_create_render_setup` still assumed a single-buffer setup always implied use of an srgb format --- include/render/vulkan.h | 1 + render/vulkan/renderer.c | 19 ++++++++++++------- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/include/render/vulkan.h b/include/render/vulkan.h index 3d1b33ba6..3dc389961 100644 --- a/include/render/vulkan.h +++ b/include/render/vulkan.h @@ -203,6 +203,7 @@ struct wlr_vk_render_format_setup { struct wl_list link; // wlr_vk_renderer.render_format_setups const struct wlr_vk_format *render_format; // used in renderpass bool use_blending_buffer; + bool use_srgb; VkRenderPass render_pass; VkPipeline output_pipe_identity; diff --git a/render/vulkan/renderer.c b/render/vulkan/renderer.c index 2f5aeb331..0b411f5dd 100644 --- a/render/vulkan/renderer.c +++ b/render/vulkan/renderer.c @@ -58,7 +58,7 @@ struct wlr_vk_renderer *vulkan_get_renderer(struct wlr_renderer *wlr_renderer) { static struct wlr_vk_render_format_setup *find_or_create_render_setup( struct wlr_vk_renderer *renderer, const struct wlr_vk_format *format, - bool has_blending_buffer); + bool has_blending_buffer, bool srgb); static struct wlr_vk_descriptor_pool *alloc_ds( struct wlr_vk_renderer *renderer, VkDescriptorSet *ds, @@ -690,7 +690,7 @@ bool vulkan_setup_two_pass_framebuffer(struct wlr_vk_render_buffer *buffer, } buffer->two_pass.render_setup = find_or_create_render_setup( - renderer, &fmt->format, true); + renderer, &fmt->format, true, false); if (!buffer->two_pass.render_setup) { goto error; } @@ -862,7 +862,7 @@ bool vulkan_setup_one_pass_framebuffer(struct wlr_vk_render_buffer *buffer, } struct wlr_vk_render_format_setup *render_setup = - find_or_create_render_setup(renderer, &fmt->format, false); + find_or_create_render_setup(renderer, &fmt->format, false, srgb); if (!render_setup) { goto error; } @@ -2225,11 +2225,12 @@ static bool init_static_render_data(struct wlr_vk_renderer *renderer) { static struct wlr_vk_render_format_setup *find_or_create_render_setup( struct wlr_vk_renderer *renderer, const struct wlr_vk_format *format, - bool use_blending_buffer) { + bool use_blending_buffer, bool srgb) { struct wlr_vk_render_format_setup *setup; wl_list_for_each(setup, &renderer->render_format_setups, link) { if (setup->render_format == format && - setup->use_blending_buffer == use_blending_buffer) { + setup->use_blending_buffer == use_blending_buffer && + setup->use_srgb == srgb) { return setup; } } @@ -2242,6 +2243,7 @@ static struct wlr_vk_render_format_setup *find_or_create_render_setup( setup->render_format = format; setup->use_blending_buffer = use_blending_buffer; + setup->use_srgb = srgb; setup->renderer = renderer; wl_list_init(&setup->pipelines); @@ -2390,9 +2392,8 @@ static struct wlr_vk_render_format_setup *find_or_create_render_setup( goto error; } } else { - assert(format->vk_srgb); VkAttachmentDescription attachment = { - .format = format->vk_srgb, + .format = format->vk, .samples = VK_SAMPLE_COUNT_1_BIT, .loadOp = VK_ATTACHMENT_LOAD_OP_LOAD, .storeOp = VK_ATTACHMENT_STORE_OP_STORE, @@ -2401,6 +2402,10 @@ static struct wlr_vk_render_format_setup *find_or_create_render_setup( .initialLayout = VK_IMAGE_LAYOUT_GENERAL, .finalLayout = VK_IMAGE_LAYOUT_GENERAL, }; + if (srgb) { + assert(format->vk_srgb); + attachment.format = format->vk_srgb; + } VkAttachmentReference color_ref = { .attachment = 0u, From bd861267e3849a3d9171050f15dfbaf090678061 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Sat, 22 Nov 2025 22:55:37 +0100 Subject: [PATCH 171/311] input-method-unstable-v2: There are no enable / disable events MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit So don't mention them Signed-off-by: Guido Günther --- protocol/input-method-unstable-v2.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocol/input-method-unstable-v2.xml b/protocol/input-method-unstable-v2.xml index 51bccf281..e7afd1918 100644 --- a/protocol/input-method-unstable-v2.xml +++ b/protocol/input-method-unstable-v2.xml @@ -82,7 +82,7 @@ This event serves the purpose of providing the compositor with an active input method. - This event resets all state associated with previous enable, disable, + This event resets all state associated with previous surrounding_text, text_change_cause, and content_type events, as well as the state associated with set_preedit_string, commit_string, and delete_surrounding_text requests. In addition, it marks the From 9b9d7d845db1917644045a52a4047bbb15ff68b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guido=20G=C3=BCnther?= Date: Sat, 22 Nov 2025 22:55:58 +0100 Subject: [PATCH 172/311] input-method-unstable-v2: surrounding text sends surrounding text MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Don't claim it sends preedit Signed-off-by: Guido Günther --- protocol/input-method-unstable-v2.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocol/input-method-unstable-v2.xml b/protocol/input-method-unstable-v2.xml index e7afd1918..84d04703a 100644 --- a/protocol/input-method-unstable-v2.xml +++ b/protocol/input-method-unstable-v2.xml @@ -120,7 +120,7 @@ If any preedit text is present, it is replaced with the cursor for the purpose of this event. - The argument text is a buffer containing the preedit string, and must + The argument text is a buffer containing the surrounding text, and must include the cursor position, and the complete selection. It should contain additional characters before and after these. There is a maximum length of wayland messages, so text can not be longer than 4000 From 17f3f2886509930ebadd709aa05af4fe81d54174 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Poisot?= Date: Sat, 1 Nov 2025 15:15:51 +0000 Subject: [PATCH 173/311] render/color: add wlr_color_primaries_transform_absolute_colorimetric --- include/wlr/render/color.h | 7 +++++++ render/color.c | 12 ++++++++++++ render/vulkan/pass.c | 19 +++---------------- 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/include/wlr/render/color.h b/include/wlr/render/color.h index b97e4c91d..44bbedfb8 100644 --- a/include/wlr/render/color.h +++ b/include/wlr/render/color.h @@ -165,4 +165,11 @@ void wlr_color_transform_unref(struct wlr_color_transform *tr); void wlr_color_transform_eval(struct wlr_color_transform *tr, float out[static 3], const float in[static 3]); +/** + * Compute the matrix to convert between two linear RGB color spaces + */ +void wlr_color_primaries_transform_absolute_colorimetric( + const struct wlr_color_primaries *source, + const struct wlr_color_primaries *destination, float matrix[static 9]); + #endif diff --git a/render/color.c b/render/color.c index 0a1a67be3..662c5b480 100644 --- a/render/color.c +++ b/render/color.c @@ -311,6 +311,18 @@ void wlr_color_primaries_to_xyz(const struct wlr_color_primaries *primaries, flo memcpy(matrix, result, sizeof(result)); } +void wlr_color_primaries_transform_absolute_colorimetric( + const struct wlr_color_primaries *source, + const struct wlr_color_primaries *destination, float matrix[static 9]) { + float source_to_xyz[9]; + wlr_color_primaries_to_xyz(source, source_to_xyz); + float destination_to_xyz[9]; + wlr_color_primaries_to_xyz(destination, destination_to_xyz); + float xyz_to_destination[9]; + matrix_invert(xyz_to_destination, destination_to_xyz); + wlr_matrix_multiply(matrix, xyz_to_destination, source_to_xyz); +} + void wlr_color_transfer_function_get_default_luminance(enum wlr_color_transfer_function tf, struct wlr_color_luminances *lum) { switch (tf) { diff --git a/render/vulkan/pass.c b/render/vulkan/pass.c index c69d48e6c..67b4280f6 100644 --- a/render/vulkan/pass.c +++ b/render/vulkan/pass.c @@ -209,14 +209,7 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { struct wlr_color_primaries srgb; wlr_color_primaries_from_named(&srgb, WLR_COLOR_NAMED_PRIMARIES_SRGB); - float srgb_to_xyz[9]; - wlr_color_primaries_to_xyz(&srgb, srgb_to_xyz); - float dst_primaries_to_xyz[9]; - wlr_color_primaries_to_xyz(&pass->primaries, dst_primaries_to_xyz); - float xyz_to_dst_primaries[9]; - matrix_invert(xyz_to_dst_primaries, dst_primaries_to_xyz); - - wlr_matrix_multiply(matrix, xyz_to_dst_primaries, srgb_to_xyz); + wlr_color_primaries_transform_absolute_colorimetric(&srgb, &pass->primaries, matrix); } else { wlr_matrix_identity(matrix); } @@ -850,14 +843,8 @@ static void render_pass_add_texture(struct wlr_render_pass *wlr_pass, struct wlr_color_primaries srgb; wlr_color_primaries_from_named(&srgb, WLR_COLOR_NAMED_PRIMARIES_SRGB); - float src_primaries_to_xyz[9]; - wlr_color_primaries_to_xyz(options->primaries, src_primaries_to_xyz); - float srgb_to_xyz[9]; - wlr_color_primaries_to_xyz(&srgb, srgb_to_xyz); - float xyz_to_srgb[9]; - matrix_invert(xyz_to_srgb, srgb_to_xyz); - - wlr_matrix_multiply(color_matrix, xyz_to_srgb, src_primaries_to_xyz); + wlr_color_primaries_transform_absolute_colorimetric(options->primaries, + &srgb, color_matrix); } else { wlr_matrix_identity(color_matrix); } From 2995f31102a022e4373d4fda83f80e11229b7ca3 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Tue, 28 Oct 2025 18:37:37 +0100 Subject: [PATCH 174/311] render/color: introduce wlr_color_transform_matrix This is useful to perform color space conversions. --- include/render/color.h | 7 +++++++ include/wlr/render/color.h | 6 ++++++ render/color.c | 19 ++++++++++++++++++- 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/include/render/color.h b/include/render/color.h index 128b345e1..111adb271 100644 --- a/include/render/color.h +++ b/include/render/color.h @@ -9,6 +9,7 @@ enum wlr_color_transform_type { COLOR_TRANSFORM_INVERSE_EOTF, COLOR_TRANSFORM_LCMS2, COLOR_TRANSFORM_LUT_3X1D, + COLOR_TRANSFORM_MATRIX, COLOR_TRANSFORM_PIPELINE, }; @@ -40,6 +41,12 @@ struct wlr_color_transform_lut_3x1d { size_t dim; }; +struct wlr_color_transform_matrix { + struct wlr_color_transform base; + + float matrix[9]; +}; + struct wlr_color_transform_pipeline { struct wlr_color_transform base; diff --git a/include/wlr/render/color.h b/include/wlr/render/color.h index 44bbedfb8..9d9363b0f 100644 --- a/include/wlr/render/color.h +++ b/include/wlr/render/color.h @@ -141,6 +141,12 @@ struct wlr_color_transform *wlr_color_transform_init_linear_to_inverse_eotf( struct wlr_color_transform *wlr_color_transform_init_lut_3x1d(size_t dim, const uint16_t *r, const uint16_t *g, const uint16_t *b); +/** + * Initialize a color transformation to apply a 3×3 matrix. Returns NULL on + * failure. + */ +struct wlr_color_transform *wlr_color_transform_init_matrix(const float matrix[static 9]); + /** * Initialize a color transformation to apply a sequence of color transforms * one after another. diff --git a/render/color.c b/render/color.c index 662c5b480..4286bc86d 100644 --- a/render/color.c +++ b/render/color.c @@ -62,6 +62,16 @@ struct wlr_color_transform *wlr_color_transform_init_lut_3x1d(size_t dim, return &tx->base; } +struct wlr_color_transform *wlr_color_transform_init_matrix(const float matrix[static 9]) { + struct wlr_color_transform_matrix *tx = calloc(1, sizeof(*tx)); + if (!tx) { + return NULL; + } + wlr_color_transform_init(&tx->base, COLOR_TRANSFORM_MATRIX); + memcpy(tx->matrix, matrix, sizeof(tx->matrix)); + return &tx->base; +} + struct wlr_color_transform *wlr_color_transform_init_pipeline( struct wlr_color_transform **transforms, size_t len) { assert(len > 0); @@ -92,6 +102,7 @@ struct wlr_color_transform *wlr_color_transform_init_pipeline( static void color_transform_destroy(struct wlr_color_transform *tr) { switch (tr->type) { case COLOR_TRANSFORM_INVERSE_EOTF: + case COLOR_TRANSFORM_MATRIX: break; case COLOR_TRANSFORM_LCMS2: color_transform_lcms2_finish(color_transform_lcms2_from_base(tr)); @@ -226,6 +237,8 @@ static void color_transform_lut_3x1d_eval(struct wlr_color_transform_lut_3x1d *t } } +static void multiply_matrix_vector(float out[static 3], float m[static 9], const float v[static 3]); + void wlr_color_transform_eval(struct wlr_color_transform *tr, float out[static 3], const float in[static 3]) { switch (tr->type) { @@ -238,6 +251,10 @@ void wlr_color_transform_eval(struct wlr_color_transform *tr, case COLOR_TRANSFORM_LUT_3X1D: color_transform_lut_3x1d_eval(color_transform_lut_3x1d_from_base(tr), out, in); break; + case COLOR_TRANSFORM_MATRIX:; + struct wlr_color_transform_matrix *matrix = wl_container_of(tr, matrix, base); + multiply_matrix_vector(out, matrix->matrix, in); + break; case COLOR_TRANSFORM_PIPELINE:; struct wlr_color_transform_pipeline *pipeline = wl_container_of(tr, pipeline, base); @@ -264,7 +281,7 @@ void wlr_color_primaries_from_named(struct wlr_color_primaries *out, abort(); } -static void multiply_matrix_vector(float out[static 3], float m[static 9], float v[static 3]) { +static void multiply_matrix_vector(float out[static 3], float m[static 9], const float v[static 3]) { float result[3] = { m[0] * v[0] + m[1] * v[1] + m[2] * v[2], m[3] * v[0] + m[4] * v[1] + m[5] * v[2], From 2cac711e5564d279e12b9fbbb039470c3feee7bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Poisot?= Date: Sun, 2 Nov 2025 13:44:48 +0000 Subject: [PATCH 175/311] render/vulkan: apply "matrix" color transforms in shader --- include/render/vulkan.h | 6 +++- render/vulkan/pass.c | 76 ++++++++++++++++++++++++++++++++--------- 2 files changed, 65 insertions(+), 17 deletions(-) diff --git a/include/render/vulkan.h b/include/render/vulkan.h index 3dc389961..bbb0495a6 100644 --- a/include/render/vulkan.h +++ b/include/render/vulkan.h @@ -553,11 +553,12 @@ struct wlr_vk_buffer_span { }; -// Lookup table for a color transform +// Prepared form for a color transform struct wlr_vk_color_transform { struct wlr_addon addon; // owned by: wlr_vk_renderer struct wl_list link; // wlr_vk_renderer, list of all color transforms + // if populated, carries the entire transform, other parameters are to be ignored struct { size_t dim; VkImage image; @@ -566,6 +567,9 @@ struct wlr_vk_color_transform { VkDescriptorSet ds; struct wlr_vk_descriptor_pool *ds_pool; } lut_3d; + + float color_matrix[9]; + enum wlr_color_transfer_function inverse_eotf; }; void vk_color_transform_destroy(struct wlr_addon *addon); diff --git a/render/vulkan/pass.c b/render/vulkan/pass.c index 67b4280f6..4c745ffae 100644 --- a/render/vulkan/pass.c +++ b/render/vulkan/pass.c @@ -151,6 +151,46 @@ static float get_luminance_multiplier(const struct wlr_color_luminances *src_lum return (dst_lum->reference / src_lum->reference) * (src_lum->max / dst_lum->max); } +static bool unwrap_color_transform(struct wlr_color_transform *transform, + float matrix[static 9], enum wlr_color_transfer_function *tf) { + if (transform == NULL) { + wlr_matrix_identity(matrix); + *tf = WLR_COLOR_TRANSFER_FUNCTION_GAMMA22; + return true; + } + struct wlr_color_transform_inverse_eotf *eotf; + struct wlr_color_transform_matrix *as_matrix; + struct wlr_color_transform_pipeline *pipeline; + switch (transform->type) { + case COLOR_TRANSFORM_INVERSE_EOTF: + eotf = wlr_color_transform_inverse_eotf_from_base(transform); + wlr_matrix_identity(matrix); + *tf = eotf->tf; + return true; + case COLOR_TRANSFORM_MATRIX: + as_matrix = wl_container_of(transform, as_matrix, base); + memcpy(matrix, as_matrix->matrix, sizeof(float[9])); + *tf = WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR; + return true; + case COLOR_TRANSFORM_PIPELINE: + pipeline = wl_container_of(transform, pipeline, base); + if (pipeline->len != 2 + || pipeline->transforms[0]->type != COLOR_TRANSFORM_MATRIX + || pipeline->transforms[1]->type != COLOR_TRANSFORM_INVERSE_EOTF) { + return false; + } + as_matrix = wl_container_of(pipeline->transforms[0], as_matrix, base); + eotf = wlr_color_transform_inverse_eotf_from_base(pipeline->transforms[1]); + memcpy(matrix, as_matrix->matrix, sizeof(float[9])); + *tf = eotf->tf; + return true; + case COLOR_TRANSFORM_LCMS2: + case COLOR_TRANSFORM_LUT_3X1D: + return false; + } + return false; +} + static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { struct wlr_vk_render_pass *pass = get_render_pass(wlr_pass); struct wlr_vk_renderer *renderer = pass->renderer; @@ -190,12 +230,21 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { }; encode_proj_matrix(final_matrix, vert_pcr_data.mat4); - struct wlr_vk_color_transform *transform = NULL; + float matrix[9]; + enum wlr_color_transfer_function tf = WLR_COLOR_TRANSFER_FUNCTION_GAMMA22; + bool need_lut = false; size_t dim = 1; - if (pass->color_transform && pass->color_transform->type != COLOR_TRANSFORM_INVERSE_EOTF) { + struct wlr_vk_color_transform *transform = NULL; + if (pass->color_transform != NULL) { transform = get_color_transform(pass->color_transform, renderer); assert(transform); - dim = transform->lut_3d.dim; + need_lut = transform->lut_3d.dim > 0; + dim = need_lut ? transform->lut_3d.dim : 1; + memcpy(matrix, transform->color_matrix, sizeof(matrix)); + tf = transform->inverse_eotf; + } + if (pass->color_transform == NULL || need_lut) { + wlr_matrix_identity(matrix); } struct wlr_vk_frag_output_pcr_data frag_pcr_data = { @@ -204,28 +253,20 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { .lut_3d_scale = (float)(dim - 1) / dim, }; - float matrix[9]; if (pass->has_primaries) { + // overwrite matrix from color_tranform, if any struct wlr_color_primaries srgb; wlr_color_primaries_from_named(&srgb, WLR_COLOR_NAMED_PRIMARIES_SRGB); wlr_color_primaries_transform_absolute_colorimetric(&srgb, &pass->primaries, matrix); - } else { - wlr_matrix_identity(matrix); } + encode_color_matrix(matrix, frag_pcr_data.matrix); VkPipeline pipeline = VK_NULL_HANDLE; - if (pass->color_transform && pass->color_transform->type != COLOR_TRANSFORM_INVERSE_EOTF) { + if (need_lut) { pipeline = render_buffer->two_pass.render_setup->output_pipe_lut3d; } else { - enum wlr_color_transfer_function tf = WLR_COLOR_TRANSFER_FUNCTION_GAMMA22; - if (pass->color_transform && pass->color_transform->type == COLOR_TRANSFORM_INVERSE_EOTF) { - struct wlr_color_transform_inverse_eotf *inverse_eotf = - wlr_color_transform_inverse_eotf_from_base(pass->color_transform); - tf = inverse_eotf->tf; - } - switch (tf) { case WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR: pipeline = render_buffer->two_pass.render_setup->output_pipe_identity; @@ -258,7 +299,7 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { sizeof(frag_pcr_data), &frag_pcr_data); VkDescriptorSet lut_ds; - if (transform != NULL) { + if (need_lut) { lut_ds = transform->lut_3d.ds; } else { lut_ds = renderer->output_ds_lut3d_dummy; @@ -1132,7 +1173,10 @@ static struct wlr_vk_color_transform *vk_color_transform_create( return NULL; } - if (transform->type != COLOR_TRANSFORM_INVERSE_EOTF) { + bool need_lut = !unwrap_color_transform(transform, vk_transform->color_matrix, + &vk_transform->inverse_eotf); + + if (need_lut) { vk_transform->lut_3d.dim = 33; if (!create_3d_lut_image(renderer, transform, vk_transform->lut_3d.dim, From fdb473e67509fafd9eb885e22605b8f1d50de001 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Poisot?= Date: Fri, 21 Nov 2025 17:12:25 +0000 Subject: [PATCH 176/311] scene: always apply user gamma after scene color transform Fixes 989cffe, making renderer-applied transform consistent with hardware-applied transform --- types/scene/wlr_scene.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/scene/wlr_scene.c b/types/scene/wlr_scene.c index 0c854c5b7..447b5b162 100644 --- a/types/scene/wlr_scene.c +++ b/types/scene/wlr_scene.c @@ -2171,8 +2171,8 @@ static struct wlr_color_transform *scene_output_combine_color_transforms( combined = wlr_color_transform_ref(gamma_lut); } else { struct wlr_color_transform *transforms[] = { - gamma_lut, supplied, + gamma_lut, }; size_t transforms_len = sizeof(transforms) / sizeof(transforms[0]); combined = wlr_color_transform_init_pipeline(transforms, transforms_len); From 3d3d5fb1b7fbf7d3cdfcd597d8b21ea02eee962e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Poisot?= Date: Wed, 12 Nov 2025 13:45:14 +0000 Subject: [PATCH 177/311] render: remove buffer primaries from pass options colorspace conversion is now carried explicitely by setting a wlr_color_transform_matrix in the color transform --- include/render/vulkan.h | 3 --- include/wlr/render/pass.h | 2 -- render/vulkan/pass.c | 12 ------------ types/output/cursor.c | 21 +++++++++++++++------ types/scene/wlr_scene.c | 20 ++++++++++++++------ 5 files changed, 29 insertions(+), 29 deletions(-) diff --git a/include/render/vulkan.h b/include/render/vulkan.h index bbb0495a6..ac0af65fb 100644 --- a/include/render/vulkan.h +++ b/include/render/vulkan.h @@ -430,9 +430,6 @@ struct wlr_vk_render_pass { bool two_pass; // rendering via intermediate blending buffer struct wlr_color_transform *color_transform; - bool has_primaries; - struct wlr_color_primaries primaries; - struct wlr_drm_syncobj_timeline *signal_timeline; uint64_t signal_point; diff --git a/include/wlr/render/pass.h b/include/wlr/render/pass.h index 9cce7c0a8..1ef888f46 100644 --- a/include/wlr/render/pass.h +++ b/include/wlr/render/pass.h @@ -35,8 +35,6 @@ struct wlr_buffer_pass_options { * Leave NULL to indicate the default transform (Gamma 2.2 encoding for * sRGB monitors) */ struct wlr_color_transform *color_transform; - /** Primaries describing the color volume of the destination buffer */ - const struct wlr_color_primaries *primaries; /* Signal a timeline synchronization point when the render pass completes. * diff --git a/render/vulkan/pass.c b/render/vulkan/pass.c index 4c745ffae..db7a659d6 100644 --- a/render/vulkan/pass.c +++ b/render/vulkan/pass.c @@ -253,14 +253,6 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { .lut_3d_scale = (float)(dim - 1) / dim, }; - if (pass->has_primaries) { - // overwrite matrix from color_tranform, if any - struct wlr_color_primaries srgb; - wlr_color_primaries_from_named(&srgb, WLR_COLOR_NAMED_PRIMARIES_SRGB); - - wlr_color_primaries_transform_absolute_colorimetric(&srgb, &pass->primaries, matrix); - } - encode_color_matrix(matrix, frag_pcr_data.matrix); VkPipeline pipeline = VK_NULL_HANDLE; @@ -1284,10 +1276,6 @@ struct wlr_vk_render_pass *vulkan_begin_render_pass(struct wlr_vk_renderer *rend pass->signal_timeline = wlr_drm_syncobj_timeline_ref(options->signal_timeline); pass->signal_point = options->signal_point; } - if (options != NULL && options->primaries != NULL) { - pass->has_primaries = true; - pass->primaries = *options->primaries; - } rect_union_init(&pass->updated_region); diff --git a/types/output/cursor.c b/types/output/cursor.c index a24f4abd1..1b33eb335 100644 --- a/types/output/cursor.c +++ b/types/output/cursor.c @@ -257,13 +257,22 @@ static struct wlr_buffer *render_cursor_buffer(struct wlr_output_cursor *cursor) buffer->width, buffer->height); struct wlr_buffer_pass_options options = {0}; - struct wlr_color_primaries primaries_value; if (output->image_description != NULL) { - options.color_transform = wlr_color_transform_init_linear_to_inverse_eotf( - output->image_description->transfer_function); - wlr_color_primaries_from_named(&primaries_value, - output->image_description->primaries); - options.primaries = &primaries_value; + struct wlr_color_primaries primaries_srgb; + wlr_color_primaries_from_named(&primaries_srgb, WLR_COLOR_NAMED_PRIMARIES_SRGB); + struct wlr_color_primaries primaries; + wlr_color_primaries_from_named(&primaries, output->image_description->primaries); + float matrix[9]; + wlr_color_primaries_transform_absolute_colorimetric(&primaries_srgb, &primaries, matrix); + struct wlr_color_transform *transforms[] = { + wlr_color_transform_init_matrix(matrix), + wlr_color_transform_init_linear_to_inverse_eotf( + output->image_description->transfer_function), + }; + size_t transform_count = sizeof(transforms) / sizeof(transforms[0]); + options.color_transform = wlr_color_transform_init_pipeline(transforms, transform_count); + wlr_color_transform_unref(transforms[0]); + wlr_color_transform_unref(transforms[1]); } struct wlr_render_pass *pass = wlr_renderer_begin_buffer_pass(renderer, buffer, &options); wlr_color_transform_unref(options.color_transform); diff --git a/types/scene/wlr_scene.c b/types/scene/wlr_scene.c index 447b5b162..ce6348dc2 100644 --- a/types/scene/wlr_scene.c +++ b/types/scene/wlr_scene.c @@ -2385,13 +2385,22 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, } struct wlr_color_transform *color_transform = NULL; - const struct wlr_color_primaries *primaries = NULL; - struct wlr_color_primaries primaries_value; const struct wlr_output_image_description *img_desc = output_pending_image_description(output, state); if (img_desc != NULL) { - color_transform = wlr_color_transform_init_linear_to_inverse_eotf(img_desc->transfer_function); - wlr_color_primaries_from_named(&primaries_value, img_desc->primaries); - primaries = &primaries_value; + struct wlr_color_primaries primaries_srgb; + wlr_color_primaries_from_named(&primaries_srgb, WLR_COLOR_NAMED_PRIMARIES_SRGB); + struct wlr_color_primaries primaries; + wlr_color_primaries_from_named(&primaries, img_desc->primaries); + float matrix[9]; + wlr_color_primaries_transform_absolute_colorimetric(&primaries_srgb, &primaries, matrix); + struct wlr_color_transform *transforms[] = { + wlr_color_transform_init_matrix(matrix), + wlr_color_transform_init_linear_to_inverse_eotf(img_desc->transfer_function), + }; + size_t transform_count = sizeof(transforms) / sizeof(transforms[0]); + color_transform = wlr_color_transform_init_pipeline(transforms, transform_count); + wlr_color_transform_unref(transforms[0]); + wlr_color_transform_unref(transforms[1]); } if (options->color_transform != NULL) { assert(color_transform == NULL); @@ -2414,7 +2423,6 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, &(struct wlr_buffer_pass_options){ .timer = timer ? timer->render_timer : NULL, .color_transform = color_transform, - .primaries = primaries, .signal_timeline = scene_output->in_timeline, .signal_point = scene_output->in_point, }); From 7101a6980419cfea5809cb88752738cc32a69b18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Poisot?= Date: Wed, 12 Nov 2025 20:15:17 +0000 Subject: [PATCH 178/311] scene: don't rebuild color transforms each frame --- include/wlr/types/wlr_scene.h | 2 +- types/scene/wlr_scene.c | 122 +++++++++++++++++++--------------- 2 files changed, 68 insertions(+), 56 deletions(-) diff --git a/include/wlr/types/wlr_scene.h b/include/wlr/types/wlr_scene.h index 83e5ad34a..2363c93ce 100644 --- a/include/wlr/types/wlr_scene.h +++ b/include/wlr/types/wlr_scene.h @@ -258,7 +258,7 @@ struct wlr_scene_output { struct wlr_color_transform *prev_gamma_lut_color_transform; struct wlr_color_transform *prev_supplied_color_transform; - struct wlr_color_transform *prev_combined_color_transform; + struct wlr_color_transform *combined_color_transform; struct wl_listener output_commit; struct wl_listener output_damage; diff --git a/types/scene/wlr_scene.c b/types/scene/wlr_scene.c index ce6348dc2..cb374258d 100644 --- a/types/scene/wlr_scene.c +++ b/types/scene/wlr_scene.c @@ -1795,7 +1795,7 @@ void wlr_scene_output_destroy(struct wlr_scene_output *scene_output) { wlr_color_transform_unref(scene_output->gamma_lut_color_transform); wlr_color_transform_unref(scene_output->prev_gamma_lut_color_transform); wlr_color_transform_unref(scene_output->prev_supplied_color_transform); - wlr_color_transform_unref(scene_output->prev_combined_color_transform); + wlr_color_transform_unref(scene_output->combined_color_transform); wl_array_release(&scene_output->render_list); free(scene_output); } @@ -2156,39 +2156,73 @@ static void scene_output_state_attempt_gamma(struct wlr_scene_output *scene_outp wlr_output_state_finish(&gamma_pending); } -static struct wlr_color_transform *scene_output_combine_color_transforms( - struct wlr_scene_output *scene_output, struct wlr_color_transform *supplied) { - struct wlr_color_transform *gamma_lut = scene_output->gamma_lut_color_transform; - assert(gamma_lut != NULL); +static bool scene_output_combine_color_transforms( + struct wlr_scene_output *scene_output, struct wlr_color_transform *supplied, + const struct wlr_output_image_description *img_desc, bool render_gamma_lut) { + struct wlr_color_transform *transforms[3] = {0}; + const size_t transforms_cap = sizeof(transforms) / sizeof(transforms[0]); + size_t transforms_len = 0; - if (gamma_lut == scene_output->prev_gamma_lut_color_transform && - supplied == scene_output->prev_supplied_color_transform) { - return wlr_color_transform_ref(scene_output->prev_combined_color_transform); + if (img_desc != NULL) { + assert(supplied == NULL); + struct wlr_color_primaries primaries_srgb; + wlr_color_primaries_from_named(&primaries_srgb, WLR_COLOR_NAMED_PRIMARIES_SRGB); + struct wlr_color_primaries primaries; + wlr_color_primaries_from_named(&primaries, img_desc->primaries); + float matrix[9]; + wlr_color_primaries_transform_absolute_colorimetric(&primaries_srgb, &primaries, matrix); + assert(transforms_len < transforms_cap); + transforms[transforms_len++] = wlr_color_transform_init_matrix(matrix); + assert(transforms_len < transforms_cap); + transforms[transforms_len++] = wlr_color_transform_init_linear_to_inverse_eotf( + img_desc->transfer_function); + } else if (supplied != NULL) { + assert(transforms_len < transforms_cap); + transforms[transforms_len++] = wlr_color_transform_ref(supplied); + } else { + assert(transforms_len < transforms_cap); + transforms[transforms_len++] = wlr_color_transform_init_linear_to_inverse_eotf( + WLR_COLOR_TRANSFER_FUNCTION_GAMMA22); } - struct wlr_color_transform *combined; - if (supplied == NULL) { - combined = wlr_color_transform_ref(gamma_lut); - } else { - struct wlr_color_transform *transforms[] = { - supplied, - gamma_lut, - }; - size_t transforms_len = sizeof(transforms) / sizeof(transforms[0]); - combined = wlr_color_transform_init_pipeline(transforms, transforms_len); - if (combined == NULL) { - return NULL; + struct wlr_color_transform *gamma_lut = scene_output->gamma_lut_color_transform; + if (gamma_lut != NULL && render_gamma_lut) { + assert(transforms_len < transforms_cap); + transforms[transforms_len++] = wlr_color_transform_ref(gamma_lut); + } + + for (size_t i = 0; i < transforms_len; ++i) { + if (transforms[i] == NULL) { + goto err_transforms; } } + struct wlr_color_transform *combined; + if (transforms_len == 1) { + combined = wlr_color_transform_ref(transforms[0]); + } else { + combined = wlr_color_transform_init_pipeline(transforms, transforms_len); + } + if (combined == NULL) { + goto err_transforms; + } + for (size_t i = 0; i < transforms_len; ++i) { + wlr_color_transform_unref(transforms[i]); + } wlr_color_transform_unref(scene_output->prev_gamma_lut_color_transform); - scene_output->prev_gamma_lut_color_transform = wlr_color_transform_ref(gamma_lut); + scene_output->prev_gamma_lut_color_transform = gamma_lut ? wlr_color_transform_ref(gamma_lut) : NULL; wlr_color_transform_unref(scene_output->prev_supplied_color_transform); scene_output->prev_supplied_color_transform = supplied ? wlr_color_transform_ref(supplied) : NULL; - wlr_color_transform_unref(scene_output->prev_combined_color_transform); - scene_output->prev_combined_color_transform = wlr_color_transform_ref(combined); + wlr_color_transform_unref(scene_output->combined_color_transform); + scene_output->combined_color_transform = combined; - return combined; + return true; + +err_transforms: + for (size_t i = 0; i < transforms_len; ++i) { + wlr_color_transform_unref(transforms[i]); + } + return false; } bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, @@ -2384,49 +2418,27 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, timer->pre_render_duration = timespec_to_nsec(&duration); } - struct wlr_color_transform *color_transform = NULL; - const struct wlr_output_image_description *img_desc = output_pending_image_description(output, state); - if (img_desc != NULL) { - struct wlr_color_primaries primaries_srgb; - wlr_color_primaries_from_named(&primaries_srgb, WLR_COLOR_NAMED_PRIMARIES_SRGB); - struct wlr_color_primaries primaries; - wlr_color_primaries_from_named(&primaries, img_desc->primaries); - float matrix[9]; - wlr_color_primaries_transform_absolute_colorimetric(&primaries_srgb, &primaries, matrix); - struct wlr_color_transform *transforms[] = { - wlr_color_transform_init_matrix(matrix), - wlr_color_transform_init_linear_to_inverse_eotf(img_desc->transfer_function), - }; - size_t transform_count = sizeof(transforms) / sizeof(transforms[0]); - color_transform = wlr_color_transform_init_pipeline(transforms, transform_count); - wlr_color_transform_unref(transforms[0]); - wlr_color_transform_unref(transforms[1]); - } - if (options->color_transform != NULL) { - assert(color_transform == NULL); - color_transform = wlr_color_transform_ref(options->color_transform); - } - - if (render_gamma_lut) { - struct wlr_color_transform *combined = - scene_output_combine_color_transforms(scene_output, color_transform); - wlr_color_transform_unref(color_transform); - if (combined == NULL) { + if ((render_gamma_lut + && scene_output->gamma_lut_color_transform != scene_output->prev_gamma_lut_color_transform) + || scene_output->prev_supplied_color_transform != options->color_transform + || (state->committed & WLR_OUTPUT_STATE_IMAGE_DESCRIPTION)) { + const struct wlr_output_image_description *output_description = + output_pending_image_description(output, state); + if (!scene_output_combine_color_transforms(scene_output, options->color_transform, + output_description, render_gamma_lut)) { wlr_buffer_unlock(buffer); return false; } - color_transform = combined; } scene_output->in_point++; struct wlr_render_pass *render_pass = wlr_renderer_begin_buffer_pass(output->renderer, buffer, &(struct wlr_buffer_pass_options){ .timer = timer ? timer->render_timer : NULL, - .color_transform = color_transform, + .color_transform = scene_output->combined_color_transform, .signal_timeline = scene_output->in_timeline, .signal_point = scene_output->in_point, }); - wlr_color_transform_unref(color_transform); if (render_pass == NULL) { wlr_buffer_unlock(buffer); return false; From 811765ffa089f9301eb05d7247bd8eb8c75d0e84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Poisot?= Date: Sun, 23 Nov 2025 11:03:30 +0000 Subject: [PATCH 179/311] output: don't rebuild cursor color transform for each update --- include/types/wlr_output.h | 2 ++ include/wlr/types/wlr_output.h | 1 + types/output/cursor.c | 54 ++++++++++++++++++++++------------ types/output/output.c | 5 ++++ 4 files changed, 43 insertions(+), 19 deletions(-) diff --git a/include/types/wlr_output.h b/include/types/wlr_output.h index d59b05f0b..fdbffaf9d 100644 --- a/include/types/wlr_output.h +++ b/include/types/wlr_output.h @@ -22,6 +22,8 @@ bool output_cursor_set_texture(struct wlr_output_cursor *cursor, int dst_width, int dst_height, enum wl_output_transform transform, int32_t hotspot_x, int32_t hotspot_y, struct wlr_drm_syncobj_timeline *wait_timeline, uint64_t wait_point); +bool output_cursor_refresh_color_transform(struct wlr_output_cursor *cursor, + const struct wlr_output_image_description *img_desc); void output_defer_present(struct wlr_output *output, struct wlr_output_event_present event); diff --git a/include/wlr/types/wlr_output.h b/include/wlr/types/wlr_output.h index 3cf8778a5..4485b3694 100644 --- a/include/wlr/types/wlr_output.h +++ b/include/wlr/types/wlr_output.h @@ -53,6 +53,7 @@ struct wlr_output_cursor { struct { struct wl_listener renderer_destroy; + struct wlr_color_transform *color_transform; } WLR_PRIVATE; }; diff --git a/types/output/cursor.c b/types/output/cursor.c index 1b33eb335..8a7f9e92a 100644 --- a/types/output/cursor.c +++ b/types/output/cursor.c @@ -256,26 +256,10 @@ static struct wlr_buffer *render_cursor_buffer(struct wlr_output_cursor *cursor) wlr_box_transform(&dst_box, &dst_box, wlr_output_transform_invert(output->transform), buffer->width, buffer->height); - struct wlr_buffer_pass_options options = {0}; - if (output->image_description != NULL) { - struct wlr_color_primaries primaries_srgb; - wlr_color_primaries_from_named(&primaries_srgb, WLR_COLOR_NAMED_PRIMARIES_SRGB); - struct wlr_color_primaries primaries; - wlr_color_primaries_from_named(&primaries, output->image_description->primaries); - float matrix[9]; - wlr_color_primaries_transform_absolute_colorimetric(&primaries_srgb, &primaries, matrix); - struct wlr_color_transform *transforms[] = { - wlr_color_transform_init_matrix(matrix), - wlr_color_transform_init_linear_to_inverse_eotf( - output->image_description->transfer_function), - }; - size_t transform_count = sizeof(transforms) / sizeof(transforms[0]); - options.color_transform = wlr_color_transform_init_pipeline(transforms, transform_count); - wlr_color_transform_unref(transforms[0]); - wlr_color_transform_unref(transforms[1]); - } + struct wlr_buffer_pass_options options = { + .color_transform = cursor->color_transform, + }; struct wlr_render_pass *pass = wlr_renderer_begin_buffer_pass(renderer, buffer, &options); - wlr_color_transform_unref(options.color_transform); if (pass == NULL) { wlr_buffer_unlock(buffer); return NULL; @@ -490,6 +474,7 @@ struct wlr_output_cursor *wlr_output_cursor_create(struct wlr_output *output) { wl_list_insert(&output->cursors, &cursor->link); cursor->visible = true; // default position is at (0, 0) wl_list_init(&cursor->renderer_destroy.link); + output_cursor_refresh_color_transform(cursor, output->image_description); return cursor; } @@ -509,5 +494,36 @@ void wlr_output_cursor_destroy(struct wlr_output_cursor *cursor) { } wlr_drm_syncobj_timeline_unref(cursor->wait_timeline); wl_list_remove(&cursor->link); + wlr_color_transform_unref(cursor->color_transform); free(cursor); } + +bool output_cursor_refresh_color_transform(struct wlr_output_cursor *output_cursor, + const struct wlr_output_image_description *img_desc) { + wlr_color_transform_unref(output_cursor->color_transform); + output_cursor->color_transform = NULL; + if (img_desc == NULL) { + return true; + } + + struct wlr_color_primaries primaries_srgb; + wlr_color_primaries_from_named(&primaries_srgb, WLR_COLOR_NAMED_PRIMARIES_SRGB); + struct wlr_color_primaries primaries; + wlr_color_primaries_from_named(&primaries, img_desc->primaries); + float matrix[9]; + wlr_color_primaries_transform_absolute_colorimetric(&primaries_srgb, &primaries, matrix); + struct wlr_color_transform *transforms[] = { + wlr_color_transform_init_matrix(matrix), + wlr_color_transform_init_linear_to_inverse_eotf(img_desc->transfer_function), + }; + if (transforms[0] == NULL || transforms[1] == NULL) { + wlr_color_transform_unref(transforms[0]); + wlr_color_transform_unref(transforms[1]); + return false; + } + output_cursor->color_transform = wlr_color_transform_init_pipeline(transforms, + sizeof(transforms) / sizeof(transforms[0])); + wlr_color_transform_unref(transforms[0]); + wlr_color_transform_unref(transforms[1]); + return output_cursor->color_transform != NULL; +} \ No newline at end of file diff --git a/types/output/output.c b/types/output/output.c index ca2e55538..3715950ce 100644 --- a/types/output/output.c +++ b/types/output/output.c @@ -240,6 +240,11 @@ static void output_apply_state(struct wlr_output *output, } else { output->image_description = NULL; } + + struct wlr_output_cursor *output_cursor; + wl_list_for_each(output_cursor, &output->cursors, link) { + output_cursor_refresh_color_transform(output_cursor, output->image_description); + } } if (state->committed & WLR_OUTPUT_STATE_COLOR_TRANSFORM) { From abf80b529e48823e21215a6ccc4653e2c2a4a565 Mon Sep 17 00:00:00 2001 From: tokyo4j Date: Mon, 24 Nov 2025 16:53:19 +0900 Subject: [PATCH 180/311] wlr-foreign-toplevel: avoid wl_resource_find_for_client() If the client application is composed of multiple components and they bind the manager global separately, choosing a single toplevel resource with wl_resource_find_for_client() may result in a component only seeing unknown toplevel handles from another component. Maybe we should track which toplevel handle resource originate from which manager resource so that a component never sees toplevel handles resources from another component, but it's too annoying to implement. --- types/wlr_foreign_toplevel_management_v1.c | 26 +++++++++++++--------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/types/wlr_foreign_toplevel_management_v1.c b/types/wlr_foreign_toplevel_management_v1.c index 15b18000c..c98457a3d 100644 --- a/types/wlr_foreign_toplevel_management_v1.c +++ b/types/wlr_foreign_toplevel_management_v1.c @@ -417,16 +417,17 @@ static void toplevel_resource_send_parent( return; } struct wl_client *client = wl_resource_get_client(toplevel_resource); - struct wl_resource *parent_resource = NULL; if (parent) { - parent_resource = wl_resource_find_for_client(&parent->resources, client); - if (!parent_resource) { - /* don't send an event if this client destroyed the parent handle */ - return; + struct wl_resource *parent_resource; + wl_resource_for_each(parent_resource, &parent->resources) { + if (wl_resource_get_client(parent_resource) == client) { + zwlr_foreign_toplevel_handle_v1_send_parent( + toplevel_resource, parent_resource); + } } + } else { + zwlr_foreign_toplevel_handle_v1_send_parent(toplevel_resource, NULL); } - zwlr_foreign_toplevel_handle_v1_send_parent(toplevel_resource, - parent_resource); } void wlr_foreign_toplevel_handle_v1_set_parent( @@ -624,10 +625,13 @@ static void foreign_toplevel_manager_bind(struct wl_client *client, void *data, } /* Second loop: send details about each toplevel. */ wl_list_for_each_safe(toplevel, tmp, &manager->toplevels, link) { - struct wl_resource *toplevel_resource = - wl_resource_find_for_client(&toplevel->resources, client); - toplevel_send_details_to_toplevel_resource(toplevel, - toplevel_resource); + struct wl_resource *toplevel_resource; + wl_resource_for_each(toplevel_resource, &toplevel->resources) { + if (wl_resource_get_client(toplevel_resource) == client) { + toplevel_send_details_to_toplevel_resource(toplevel, + toplevel_resource); + } + } } } From 47486545b196987f6f07fffe2929bba8f515b8e9 Mon Sep 17 00:00:00 2001 From: Dale Turner Date: Wed, 3 Dec 2025 20:02:29 -0400 Subject: [PATCH 181/311] =?UTF-8?q?Add=20"const"=20to=20eliminate=20"error?= =?UTF-8?q?:=20initialization=20discards=20=E2=80=98const=E2=80=99=20quali?= =?UTF-8?q?fier=20from=20pointer=20target=20type"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- xcursor/xcursor.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xcursor/xcursor.c b/xcursor/xcursor.c index b5edb9de8..6627fb6cd 100644 --- a/xcursor/xcursor.c +++ b/xcursor/xcursor.c @@ -602,7 +602,7 @@ xcursor_build_fullname(const char *dir, const char *subdir, const char *file) static const char * xcursor_next_path(const char *path) { - char *colon = strchr(path, ':'); + const char *colon = strchr(path, ':'); if (!colon) return NULL; From c45b3b1f5f1aa334d73688a1c16784d97c5df748 Mon Sep 17 00:00:00 2001 From: llyyr Date: Thu, 4 Dec 2025 15:27:22 +0530 Subject: [PATCH 182/311] color_representation_v1: don't leak supported_* on display destroy --- types/wlr_color_representation_v1.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/types/wlr_color_representation_v1.c b/types/wlr_color_representation_v1.c index af7fac60c..ff9b6e3b7 100644 --- a/types/wlr_color_representation_v1.c +++ b/types/wlr_color_representation_v1.c @@ -354,6 +354,8 @@ static void handle_display_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&manager->display_destroy.link); wl_global_destroy(manager->global); + free(manager->supported_alpha_modes); + free(manager->supported_coeffs_and_ranges); free(manager); } From a962d58727bf5a95ef0da50dce21031728c92f2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Poisot?= Date: Fri, 5 Dec 2025 19:14:49 +0000 Subject: [PATCH 183/311] render/color: make wlr_color_primaries_from_named public --- include/render/color.h | 6 ------ include/wlr/render/color.h | 6 ++++++ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/include/render/color.h b/include/render/color.h index 111adb271..3a672eff1 100644 --- a/include/render/color.h +++ b/include/render/color.h @@ -87,12 +87,6 @@ struct wlr_color_transform_inverse_eotf *wlr_color_transform_inverse_eotf_from_b struct wlr_color_transform_lut_3x1d *color_transform_lut_3x1d_from_base( struct wlr_color_transform *tr); -/** - * Obtain primaries values from a well-known primaries name. - */ -void wlr_color_primaries_from_named(struct wlr_color_primaries *out, - enum wlr_color_named_primaries named); - /** * Compute the matrix to convert RGB color values to CIE 1931 XYZ. */ diff --git a/include/wlr/render/color.h b/include/wlr/render/color.h index 9d9363b0f..31d2c85e9 100644 --- a/include/wlr/render/color.h +++ b/include/wlr/render/color.h @@ -171,6 +171,12 @@ void wlr_color_transform_unref(struct wlr_color_transform *tr); void wlr_color_transform_eval(struct wlr_color_transform *tr, float out[static 3], const float in[static 3]); +/** + * Obtain primaries values from a well-known primaries name. + */ +void wlr_color_primaries_from_named(struct wlr_color_primaries *out, + enum wlr_color_named_primaries named); + /** * Compute the matrix to convert between two linear RGB color spaces */ From bf9452433cac5049c74ebaf7bbbcb11e2853c6e5 Mon Sep 17 00:00:00 2001 From: llyyr Date: Fri, 5 Dec 2025 02:32:37 +0530 Subject: [PATCH 184/311] render/vulkan: normalize luminance range in bt.1886 formula BT.1886 is different from other EOTFs in that the spec says that offset b must be in electrical domain, so use the assumed display environment from the specification as Lmin and Lmax then normalize bt.1886 to produce optical luminance L in [0, 1]. See discussion at https://gitlab.freedesktop.org/pq/color-and-hdr/-/merge_requests/63#note_3090016 --- render/vulkan/shaders/output.frag | 9 ++++++--- render/vulkan/shaders/texture.frag | 9 ++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/render/vulkan/shaders/output.frag b/render/vulkan/shaders/output.frag index 5b952fcff..0a8d9b855 100644 --- a/render/vulkan/shaders/output.frag +++ b/render/vulkan/shaders/output.frag @@ -49,11 +49,14 @@ vec3 linear_color_to_pq(vec3 color) { } vec3 linear_color_to_bt1886(vec3 color) { - float lb = pow(0.0001, 1.0 / 2.4); - float lw = pow(1.0, 1.0 / 2.4); + float Lmin = 0.01; + float Lmax = 100.0; + float lb = pow(Lmin, 1.0 / 2.4); + float lw = pow(Lmax, 1.0 / 2.4); float a = pow(lw - lb, 2.4); float b = lb / (lw - lb); - return pow(color / a, vec3(1.0 / 2.4)) - vec3(b); + vec3 L = color * (Lmax - Lmin) + vec3(Lmin); + return pow(L / a, vec3(1.0 / 2.4)) - vec3(b); } void main() { diff --git a/render/vulkan/shaders/texture.frag b/render/vulkan/shaders/texture.frag index 57d2d049c..3ec974235 100644 --- a/render/vulkan/shaders/texture.frag +++ b/render/vulkan/shaders/texture.frag @@ -47,11 +47,14 @@ vec3 pq_color_to_linear(vec3 color) { } vec3 bt1886_color_to_linear(vec3 color) { - float lb = pow(0.0001, 1.0 / 2.4); - float lw = pow(1.0, 1.0 / 2.4); + float Lmin = 0.01; + float Lmax = 100.0; + float lb = pow(Lmin, 1.0 / 2.4); + float lw = pow(Lmax, 1.0 / 2.4); float a = pow(lw - lb, 2.4); float b = lb / (lw - lb); - return a * pow(color + vec3(b), vec3(2.4)); + vec3 L = a * pow(color + vec3(b), vec3(2.4)); + return (L - Lmin) / (Lmax - Lmin); } void main() { From 03b465f324f8063bb221cdf323febfa810b93811 Mon Sep 17 00:00:00 2001 From: Ilia Bozhinov Date: Sat, 20 Aug 2022 15:49:43 +0200 Subject: [PATCH 185/311] drag: destroy data source on touch_up This is in case we drop the drag with no focus, we want to destroy the drag to tell the client the drag was cancelled. --- types/data_device/wlr_drag.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/types/data_device/wlr_drag.c b/types/data_device/wlr_drag.c index 8c17eeb68..df0ce3b83 100644 --- a/types/data_device/wlr_drag.c +++ b/types/data_device/wlr_drag.c @@ -292,6 +292,10 @@ static uint32_t drag_handle_touch_up(struct wlr_seat_touch_grab *grab, if (drag->focus_client) { drag_drop(drag, time); + } else if (drag->source->impl->dnd_finish) { + // This will end the grab and free `drag` + wlr_data_source_destroy(drag->source); + return 0; } drag_destroy(drag); From 0e9c6ddefa11b56564abcf0c8317ef9f70d4f3d3 Mon Sep 17 00:00:00 2001 From: Ilia Bozhinov Date: Thu, 4 Dec 2025 11:18:22 +0100 Subject: [PATCH 186/311] seat: add wlr_seat_touch_notify_clear_focus This is needed for cases where the touch operation goes over a region where no surfaces are present. In this case, we'd want to notify the touch grabs (for example DnD grabs) that no focus is currently focused. --- include/wlr/types/wlr_seat.h | 4 ++++ types/data_device/wlr_drag.c | 7 +++++++ types/seat/wlr_seat_touch.c | 20 ++++++++++++++++++++ types/xdg_shell/wlr_xdg_popup.c | 8 +++++++- 4 files changed, 38 insertions(+), 1 deletion(-) diff --git a/include/wlr/types/wlr_seat.h b/include/wlr/types/wlr_seat.h index 74b4341dd..670b4458b 100644 --- a/include/wlr/types/wlr_seat.h +++ b/include/wlr/types/wlr_seat.h @@ -140,6 +140,8 @@ struct wlr_touch_grab_interface { // Send wl_touch.cancel void (*wl_cancel)(struct wlr_seat_touch_grab *grab, struct wlr_seat_client *seat_client); + void (*clear_focus)(struct wlr_seat_touch_grab *grab, uint32_t time_msec, + struct wlr_touch_point *point); }; /** @@ -685,6 +687,8 @@ void wlr_seat_touch_notify_cancel(struct wlr_seat *seat, struct wlr_seat_client *seat_client); void wlr_seat_touch_notify_frame(struct wlr_seat *seat); +void wlr_seat_touch_notify_clear_focus(struct wlr_seat *seat, + uint32_t time_msec, int32_t touch_id); /** * How many touch points are currently down for the seat. diff --git a/types/data_device/wlr_drag.c b/types/data_device/wlr_drag.c index df0ce3b83..dbf30a321 100644 --- a/types/data_device/wlr_drag.c +++ b/types/data_device/wlr_drag.c @@ -334,6 +334,12 @@ static void drag_handle_touch_cancel(struct wlr_seat_touch_grab *grab) { drag_destroy(drag); } +static void drag_handle_clear_focus(struct wlr_seat_touch_grab *grab, uint32_t time_msec, + struct wlr_touch_point *point) { + struct wlr_drag *drag = grab->data; + drag_set_focus(drag, NULL, 0, 0); +} + static const struct wlr_touch_grab_interface data_device_touch_drag_interface = { .down = drag_handle_touch_down, @@ -341,6 +347,7 @@ static const struct wlr_touch_grab_interface .motion = drag_handle_touch_motion, .enter = drag_handle_touch_enter, .cancel = drag_handle_touch_cancel, + .clear_focus = drag_handle_clear_focus, }; static void drag_handle_keyboard_enter(struct wlr_seat_keyboard_grab *grab, diff --git a/types/seat/wlr_seat_touch.c b/types/seat/wlr_seat_touch.c index 192ae5890..217c19e70 100644 --- a/types/seat/wlr_seat_touch.c +++ b/types/seat/wlr_seat_touch.c @@ -44,6 +44,12 @@ static void default_touch_wl_cancel(struct wlr_seat_touch_grab *grab, wlr_seat_touch_send_cancel(grab->seat, seat_client); } +static void default_touch_clear_focus(struct wlr_seat_touch_grab *grab, uint32_t time_msec, + struct wlr_touch_point *point) { + wlr_seat_touch_point_clear_focus(grab->seat, time_msec, point->touch_id); +} + + const struct wlr_touch_grab_interface default_touch_grab_impl = { .down = default_touch_down, .up = default_touch_up, @@ -52,6 +58,7 @@ const struct wlr_touch_grab_interface default_touch_grab_impl = { .frame = default_touch_frame, .cancel = default_touch_cancel, .wl_cancel = default_touch_wl_cancel, + .clear_focus = default_touch_clear_focus, }; @@ -256,6 +263,19 @@ void wlr_seat_touch_notify_cancel(struct wlr_seat *seat, } } +void wlr_seat_touch_notify_clear_focus(struct wlr_seat *seat, + uint32_t time, int32_t touch_id) { + struct wlr_seat_touch_grab *grab = seat->touch_state.grab; + struct wlr_touch_point *point = wlr_seat_touch_get_point(seat, touch_id); + if (!point) { + return; + } + + if (grab->interface->clear_focus) { + grab->interface->clear_focus(grab, time, point); + } +} + static void handle_point_focus_destroy(struct wl_listener *listener, void *data) { struct wlr_touch_point *point = diff --git a/types/xdg_shell/wlr_xdg_popup.c b/types/xdg_shell/wlr_xdg_popup.c index 25c07c8c5..0c3b9fe44 100644 --- a/types/xdg_shell/wlr_xdg_popup.c +++ b/types/xdg_shell/wlr_xdg_popup.c @@ -174,13 +174,19 @@ static void xdg_touch_grab_cancel(struct wlr_seat_touch_grab *grab) { wlr_seat_touch_end_grab(grab->seat); } +static void xdg_touch_grab_clear_focus(struct wlr_seat_touch_grab *grab, uint32_t time_msec, + struct wlr_touch_point *point) { + wlr_seat_touch_point_clear_focus(grab->seat, time_msec, point->touch_id); +} + static const struct wlr_touch_grab_interface xdg_touch_grab_impl = { .down = xdg_touch_grab_down, .up = xdg_touch_grab_up, .motion = xdg_touch_grab_motion, .enter = xdg_touch_grab_enter, .frame = xdg_touch_grab_frame, - .cancel = xdg_touch_grab_cancel + .cancel = xdg_touch_grab_cancel, + .clear_focus = xdg_touch_grab_clear_focus, }; static void destroy_xdg_popup_grab(struct wlr_xdg_popup_grab *xdg_grab) { From ba931024a514be75a8a84a21483f9a888e0a9f1c Mon Sep 17 00:00:00 2001 From: tokyo4j Date: Mon, 7 Jul 2025 04:36:56 +0900 Subject: [PATCH 187/311] Add wlr_version_get_{major,minor,micro}() Can be used to detect wlroots version at runtime rather than compile-time. --- include/wlr/version.h.in | 5 +++++ util/meson.build | 1 + util/version.c | 13 +++++++++++++ 3 files changed, 19 insertions(+) create mode 100644 util/version.c diff --git a/include/wlr/version.h.in b/include/wlr/version.h.in index dcfcb751d..10ff73469 100644 --- a/include/wlr/version.h.in +++ b/include/wlr/version.h.in @@ -9,4 +9,9 @@ #define WLR_VERSION_NUM ((WLR_VERSION_MAJOR << 16) | (WLR_VERSION_MINOR << 8) | WLR_VERSION_MICRO) +/* For runtime version detection */ +int wlr_version_get_major(void); +int wlr_version_get_minor(void); +int wlr_version_get_micro(void); + #endif diff --git a/util/meson.build b/util/meson.build index d67911e52..6aad13386 100644 --- a/util/meson.build +++ b/util/meson.build @@ -15,4 +15,5 @@ wlr_files += files( 'token.c', 'transform.c', 'utf8.c', + 'version.c', ) diff --git a/util/version.c b/util/version.c new file mode 100644 index 000000000..081146bd5 --- /dev/null +++ b/util/version.c @@ -0,0 +1,13 @@ +#include "wlr/version.h" + +int wlr_version_get_major(void) { + return WLR_VERSION_MAJOR; +} + +int wlr_version_get_minor(void) { + return WLR_VERSION_MINOR; +} + +int wlr_version_get_micro(void) { + return WLR_VERSION_MICRO; +} From 9b4d9eabb1071a63a1daa547d1264c99b49291e8 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Wed, 18 Jun 2025 22:31:30 +0200 Subject: [PATCH 188/311] color_management_v1: add helpers to get supported TFs/primaries This avoids hardcoding lists of TFs/primaries in compositors. I've considered adding wlr_color_manager_v1_create_with_renderer() instead, but I'm worried that some aspects of the options struct don't really depend on the renderer, but on the compositor. Such things are features and render intents. I've considered returning a const array, but this would tie our hands if we want to make the renderer advertise the set of TFs/primaries it supports, instead of having a single flag gating all of them. --- include/wlr/types/wlr_color_management_v1.h | 15 +++++++ types/wlr_color_management_v1.c | 47 +++++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/include/wlr/types/wlr_color_management_v1.h b/include/wlr/types/wlr_color_management_v1.h index e6cb7dfbb..8caa61c6a 100644 --- a/include/wlr/types/wlr_color_management_v1.h +++ b/include/wlr/types/wlr_color_management_v1.h @@ -14,6 +14,7 @@ #include +struct wlr_renderer; struct wlr_surface; struct wlr_image_description_v1_data { @@ -119,4 +120,18 @@ wlr_color_manager_v1_primaries_to_wlr(enum wp_color_manager_v1_primaries primari enum wp_color_manager_v1_primaries wlr_color_manager_v1_primaries_from_wlr(enum wlr_color_named_primaries primaries); +/** + * Get a list of supported transfer functions for a renderer. The caller is + * responsible for free'ing the array. + */ +enum wp_color_manager_v1_transfer_function * +wlr_color_manager_v1_transfer_function_list_from_renderer(struct wlr_renderer *renderer, size_t *len); + +/** + * Get a list of supported named primaries for a renderer. The caller is + * responsible for free'ing the array. + */ +enum wp_color_manager_v1_primaries * +wlr_color_manager_v1_primaries_list_from_renderer(struct wlr_renderer *renderer, size_t *len); + #endif diff --git a/types/wlr_color_management_v1.c b/types/wlr_color_management_v1.c index c69a69c81..14c7acabd 100644 --- a/types/wlr_color_management_v1.c +++ b/types/wlr_color_management_v1.c @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -1041,3 +1042,49 @@ wlr_color_manager_v1_primaries_from_wlr(enum wlr_color_named_primaries primaries } abort(); } + +enum wp_color_manager_v1_transfer_function * +wlr_color_manager_v1_transfer_function_list_from_renderer(struct wlr_renderer *renderer, size_t *len) { + if (!renderer->features.input_color_transform) { + *len = 0; + return NULL; + } + + const enum wp_color_manager_v1_transfer_function list[] = { + WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_SRGB, + WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA22, + WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST2084_PQ, + WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR, + }; + + enum wp_color_manager_v1_transfer_function *out = NULL; + if (!memdup(&out, list, sizeof(list))) { + *len = 0; + return NULL; + } + + *len = sizeof(list) / sizeof(list[0]); + return out; +} + +enum wp_color_manager_v1_primaries * +wlr_color_manager_v1_primaries_list_from_renderer(struct wlr_renderer *renderer, size_t *len) { + if (!renderer->features.input_color_transform) { + *len = 0; + return NULL; + } + + const enum wp_color_manager_v1_primaries list[] = { + WP_COLOR_MANAGER_V1_PRIMARIES_SRGB, + WP_COLOR_MANAGER_V1_PRIMARIES_BT2020, + }; + + enum wp_color_manager_v1_primaries *out = NULL; + if (!memdup(&out, list, sizeof(list))) { + *len = 0; + return NULL; + } + + *len = sizeof(list) / sizeof(list[0]); + return out; +} From 322291cdcfe1ea29fd6987f4922c90c5a2798d18 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Thu, 18 Dec 2025 15:22:33 +0100 Subject: [PATCH 189/311] tinywl: fix duplicate object files passed to linker On BSD make, $> is an alias for $^. On both GNU and BSD make, $^ is supported. Specifying both resulted in duplicate object files passed to the linker: ld: error: duplicate symbol: main >>> defined at tinywl.c:887 >>> tinywl.o:(main) >>> defined at tinywl.c:887 >>> tinywl.o:(.text+0x0) cc: error: linker command failed with exit code 1 (use -v to see invocation) Only use $^ and remove $>. --- tinywl/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tinywl/Makefile b/tinywl/Makefile index 9c7af540e..2efe4a436 100644 --- a/tinywl/Makefile +++ b/tinywl/Makefile @@ -10,7 +10,7 @@ all: tinywl tinywl.o: tinywl.c $(CC) -c $< -g -Werror $(CFLAGS) -I. -DWLR_USE_UNSTABLE -o $@ tinywl: tinywl.o - $(CC) $^ $> -g -Werror $(CFLAGS) $(LDFLAGS) $(LIBS) -o $@ + $(CC) $^ -g -Werror $(CFLAGS) $(LDFLAGS) $(LIBS) -o $@ clean: rm -f tinywl tinywl.o From c0b93a9e7cc5174658b0cb4e76b302d144d7f8a2 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Thu, 20 Nov 2025 17:33:59 +0100 Subject: [PATCH 190/311] render/pixman: add support for ABGR16161616 PIXMAN_a16b16g16r16 has been added in Pixman in this commit: https://gitlab.freedesktop.org/pixman/pixman/-/commit/c0d38585f14411db88ee0b824dd6ee9a2e182c2b --- render/pixman/meson.build | 2 +- render/pixman/pixel_format.c | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/render/pixman/meson.build b/render/pixman/meson.build index c8fb875c0..dea96b096 100644 --- a/render/pixman/meson.build +++ b/render/pixman/meson.build @@ -1,4 +1,4 @@ -pixman = dependency('pixman-1') +pixman = dependency('pixman-1', version: '>=0.46.0') wlr_deps += pixman diff --git a/render/pixman/pixel_format.c b/render/pixman/pixel_format.c index 5a460a0a2..c308a2b67 100644 --- a/render/pixman/pixel_format.c +++ b/render/pixman/pixel_format.c @@ -96,6 +96,10 @@ static const struct wlr_pixman_pixel_format formats[] = { .drm_format = DRM_FORMAT_XBGR2101010, .pixman_format = PIXMAN_x2b10g10r10, }, + { + .drm_format = DRM_FORMAT_ABGR16161616, + .pixman_format = PIXMAN_a16b16g16r16, + }, #endif }; From 3d8c471aef7ae06d0ab33b69836e380e818a1c6c Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Thu, 18 Dec 2025 15:10:15 +0100 Subject: [PATCH 191/311] color_management_v1: add BT.1886 to TF-from-renderer helper BT.1886 support has been added to wlroots after this helper got written. --- types/wlr_color_management_v1.c | 1 + 1 file changed, 1 insertion(+) diff --git a/types/wlr_color_management_v1.c b/types/wlr_color_management_v1.c index 14c7acabd..18ce6ec26 100644 --- a/types/wlr_color_management_v1.c +++ b/types/wlr_color_management_v1.c @@ -1055,6 +1055,7 @@ wlr_color_manager_v1_transfer_function_list_from_renderer(struct wlr_renderer *r WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA22, WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST2084_PQ, WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR, + WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_BT1886, }; enum wp_color_manager_v1_transfer_function *out = NULL; From 32d5fc1d110a08a9ab1c326aec15279f79c3e5a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Poisot?= Date: Fri, 28 Nov 2025 18:21:45 +0000 Subject: [PATCH 192/311] render/color: introduce color_transform_compose It flattens color transform pipelines, and facilitates building pipelines of variable content --- include/render/color.h | 10 +++++++ render/color.c | 65 +++++++++++++++++++++++++++++++++++++++++ types/scene/wlr_scene.c | 64 ++++++++++++++++++---------------------- 3 files changed, 104 insertions(+), 35 deletions(-) diff --git a/include/render/color.h b/include/render/color.h index 3a672eff1..57d2b6a96 100644 --- a/include/render/color.h +++ b/include/render/color.h @@ -87,6 +87,16 @@ struct wlr_color_transform_inverse_eotf *wlr_color_transform_inverse_eotf_from_b struct wlr_color_transform_lut_3x1d *color_transform_lut_3x1d_from_base( struct wlr_color_transform *tr); +/** + * Create a simplified / normalized wlr_color_transform pipeline. + * `transforms` may contain NULL transforms, they will be interpreted as the + * identity transform, and removed. + * `*result` may be set to a tranform of a type different from + * `wlr_color_transform_pipeline`, or to NULL if all input transforms are NULL + */ +bool color_transform_compose(struct wlr_color_transform **result, + struct wlr_color_transform **transforms, size_t len); + /** * Compute the matrix to convert RGB color values to CIE 1931 XYZ. */ diff --git a/render/color.c b/render/color.c index 4286bc86d..86f415374 100644 --- a/render/color.c +++ b/render/color.c @@ -268,6 +268,71 @@ void wlr_color_transform_eval(struct wlr_color_transform *tr, } } +static size_t color_transform_compose_collect(struct wlr_color_transform **out, + size_t out_capacity, struct wlr_color_transform **transforms, size_t len) { + size_t count = 0; + for (size_t i = 0; i < len; i++) { + struct wlr_color_transform *transform = transforms[i]; + if (transform == NULL) { + continue; + } + + if (transform->type == COLOR_TRANSFORM_PIPELINE) { + struct wlr_color_transform_pipeline *pipeline = wl_container_of(transform, + pipeline, base); + count += color_transform_compose_collect(out, out_capacity, + pipeline->transforms, pipeline->len); + } else { + if (out_capacity > 0) { + *out = wlr_color_transform_ref(transform); + out++; + out_capacity--; + } + count++; + } + } + return count; +} + +bool color_transform_compose(struct wlr_color_transform **result, + struct wlr_color_transform **transforms, size_t len) { + // The normalized form has the following properties : + // - No NULL transform in a pipeline + // - No pipeline of length 1 + // - No nested pipelines + bool status = false; + + size_t result_len = color_transform_compose_collect(NULL, 0, transforms, len); + if (result_len == 0) { + *result = NULL; + return true; + } + + struct wlr_color_transform **result_transforms = calloc(result_len, + sizeof(result_transforms[0])); + if (result_transforms == NULL) { + return false; + } + color_transform_compose_collect(result_transforms, result_len, transforms, len); + + if (result_len == 1) { + *result = wlr_color_transform_ref(result_transforms[0]); + } else { + *result = wlr_color_transform_init_pipeline(result_transforms, result_len); + if (*result == NULL) { + goto cleanup_transforms; + } + } + status = true; + +cleanup_transforms: + for (size_t i = 0; i < result_len; i++) { + wlr_color_transform_unref(result_transforms[i]); + } + free(result_transforms); + return status; +} + void wlr_color_primaries_from_named(struct wlr_color_primaries *out, enum wlr_color_named_primaries named) { switch (named) { diff --git a/types/scene/wlr_scene.c b/types/scene/wlr_scene.c index cb374258d..07746f68c 100644 --- a/types/scene/wlr_scene.c +++ b/types/scene/wlr_scene.c @@ -2159,9 +2159,10 @@ static void scene_output_state_attempt_gamma(struct wlr_scene_output *scene_outp static bool scene_output_combine_color_transforms( struct wlr_scene_output *scene_output, struct wlr_color_transform *supplied, const struct wlr_output_image_description *img_desc, bool render_gamma_lut) { - struct wlr_color_transform *transforms[3] = {0}; - const size_t transforms_cap = sizeof(transforms) / sizeof(transforms[0]); - size_t transforms_len = 0; + bool result = false; + struct wlr_color_transform *color_matrix = NULL; + struct wlr_color_transform *inv_eotf = NULL; + struct wlr_color_transform *user_gamma = NULL; if (img_desc != NULL) { assert(supplied == NULL); @@ -2171,42 +2172,35 @@ static bool scene_output_combine_color_transforms( wlr_color_primaries_from_named(&primaries, img_desc->primaries); float matrix[9]; wlr_color_primaries_transform_absolute_colorimetric(&primaries_srgb, &primaries, matrix); - assert(transforms_len < transforms_cap); - transforms[transforms_len++] = wlr_color_transform_init_matrix(matrix); - assert(transforms_len < transforms_cap); - transforms[transforms_len++] = wlr_color_transform_init_linear_to_inverse_eotf( - img_desc->transfer_function); + color_matrix = wlr_color_transform_init_matrix(matrix); + inv_eotf = wlr_color_transform_init_linear_to_inverse_eotf(img_desc->transfer_function); + if (color_matrix == NULL || inv_eotf == NULL) { + goto cleanup_transforms; + } } else if (supplied != NULL) { - assert(transforms_len < transforms_cap); - transforms[transforms_len++] = wlr_color_transform_ref(supplied); + inv_eotf = wlr_color_transform_ref(supplied); } else { - assert(transforms_len < transforms_cap); - transforms[transforms_len++] = wlr_color_transform_init_linear_to_inverse_eotf( + inv_eotf = wlr_color_transform_init_linear_to_inverse_eotf( WLR_COLOR_TRANSFER_FUNCTION_GAMMA22); + if (inv_eotf == NULL) { + goto cleanup_transforms; + } } struct wlr_color_transform *gamma_lut = scene_output->gamma_lut_color_transform; if (gamma_lut != NULL && render_gamma_lut) { - assert(transforms_len < transforms_cap); - transforms[transforms_len++] = wlr_color_transform_ref(gamma_lut); + user_gamma = wlr_color_transform_ref(gamma_lut); } - for (size_t i = 0; i < transforms_len; ++i) { - if (transforms[i] == NULL) { - goto err_transforms; - } - } struct wlr_color_transform *combined; - if (transforms_len == 1) { - combined = wlr_color_transform_ref(transforms[0]); - } else { - combined = wlr_color_transform_init_pipeline(transforms, transforms_len); - } - if (combined == NULL) { - goto err_transforms; - } - for (size_t i = 0; i < transforms_len; ++i) { - wlr_color_transform_unref(transforms[i]); + struct wlr_color_transform *transforms[] = { + color_matrix, + inv_eotf, + user_gamma, + }; + const size_t transforms_len = sizeof(transforms) / sizeof(transforms[0]); + if (!color_transform_compose(&combined, transforms, transforms_len)) { + goto cleanup_transforms; } wlr_color_transform_unref(scene_output->prev_gamma_lut_color_transform); @@ -2216,13 +2210,13 @@ static bool scene_output_combine_color_transforms( wlr_color_transform_unref(scene_output->combined_color_transform); scene_output->combined_color_transform = combined; - return true; + result = true; -err_transforms: - for (size_t i = 0; i < transforms_len; ++i) { - wlr_color_transform_unref(transforms[i]); - } - return false; +cleanup_transforms: + wlr_color_transform_unref(color_matrix); + wlr_color_transform_unref(inv_eotf); + wlr_color_transform_unref(user_gamma); + return result; } bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, From 450d90a55d1a5a5d70fab08dc27d2e357f2f3f53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Poisot?= Date: Fri, 28 Nov 2025 18:23:30 +0000 Subject: [PATCH 193/311] render/color: assert that wlr_color_transform_pipeline contains no NULLs Consumers of transform pipelines are not expected to handle no-op stages --- render/color.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/render/color.c b/render/color.c index 86f415374..e89fab260 100644 --- a/render/color.c +++ b/render/color.c @@ -88,8 +88,8 @@ struct wlr_color_transform *wlr_color_transform_init_pipeline( } wlr_color_transform_init(&tx->base, COLOR_TRANSFORM_PIPELINE); - // TODO: flatten nested pipeline transforms for (size_t i = 0; i < len; i++) { + assert(transforms[i] != NULL); copy[i] = wlr_color_transform_ref(transforms[i]); } From 0ad8395ae67d4cbd32b7974246ab6c91bb217b0c Mon Sep 17 00:00:00 2001 From: liupeng Date: Wed, 17 Dec 2025 20:47:33 +0800 Subject: [PATCH 194/311] backend/session: respond to event hangup or error Signed-off-by: liupeng --- backend/session/session.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/backend/session/session.c b/backend/session/session.c index 32522fb42..57b13b4ca 100644 --- a/backend/session/session.c +++ b/backend/session/session.c @@ -36,6 +36,15 @@ static void handle_disable_seat(struct libseat *seat, void *data) { static int libseat_event(int fd, uint32_t mask, void *data) { struct wlr_session *session = data; + if (mask & (WL_EVENT_HANGUP | WL_EVENT_ERROR)) { + if (mask & WL_EVENT_ERROR) { + wlr_log(WLR_ERROR, "Failed to wait for libseat event"); + } else { + wlr_log(WLR_INFO, "Failed to wait for libseat event"); + } + wlr_session_destroy(session); + return 0; + } if (libseat_dispatch(session->seat_handle, 0) == -1) { wlr_log_errno(WLR_ERROR, "Failed to dispatch libseat"); wlr_session_destroy(session); From 16cb509a6e21c8d9d74f4dfa98c7df5f176720c5 Mon Sep 17 00:00:00 2001 From: John Lindgren Date: Sat, 20 Dec 2025 14:57:24 -0500 Subject: [PATCH 195/311] render/allocator: add missing wlr_buffer_finish() in destroy impls Fixes use-after-free on exit of labwc running nested: ==50906== Invalid write of size 8 ==50906== at 0x4A85403: wl_list_remove (wayland-util.c:57) ==50906== by 0x40BBAF9: destroy_wl_buffer (output.c:146) ==50906== by 0x40B9B4F: backend_destroy (backend.c:488) ==50906== by 0x409E96F: wlr_backend_destroy (backend.c:68) ==50906== by 0x40B78A6: multi_backend_destroy (backend.c:62) ==50906== by 0x409E96F: wlr_backend_destroy (backend.c:68) ==50906== by 0x4043DA0: server_finish (server.c:788) ==50906== by 0x403AA85: main (main.c:277) ==50906== Address 0xb4435e8 is 40 bytes inside a block of size 136 free'd ==50906== at 0x4A3E8EF: free (vg_replace_malloc.c:989) ==50906== by 0x409C954: buffer_destroy (shm.c:28) ==50906== by 0x40E96F4: buffer_consider_destroy (buffer.c:42) ==50906== by 0x40E9754: wlr_buffer_drop (buffer.c:52) ==50906== by 0x41498DA: slot_reset (swapchain.c:44) ==50906== by 0x4149933: wlr_swapchain_destroy (swapchain.c:53) ==50906== by 0x40CB1FA: wlr_output_finish (output.c:410) ==50906== by 0x40BE00B: output_destroy (output.c:957) ==50906== by 0x40CB2FC: wlr_output_destroy (output.c:436) ==50906== by 0x40B9AFC: backend_destroy (backend.c:481) ==50906== by 0x409E96F: wlr_backend_destroy (backend.c:68) ==50906== by 0x40B78A6: multi_backend_destroy (backend.c:62) ==50906== Block was alloc'd at ==50906== at 0x4A42C13: calloc (vg_replace_malloc.c:1675) ==50906== by 0x409CA84: allocator_create_buffer (shm.c:68) ==50906== by 0x409C7BA: wlr_allocator_create_buffer (allocator.c:186) ==50906== by 0x4149B80: wlr_swapchain_acquire (swapchain.c:102) ==50906== by 0x40C90DA: render_cursor_buffer (cursor.c:246) ==50906== by 0x40C93DC: output_cursor_attempt_hardware (cursor.c:303) ==50906== by 0x40C9A61: output_cursor_set_texture (cursor.c:420) ==50906== by 0x40C9738: wlr_output_cursor_set_buffer (cursor.c:352) ==50906== by 0x40F13A0: output_cursor_set_xcursor_image (wlr_cursor.c:507) ==50906== by 0x40F1B28: cursor_output_cursor_update (wlr_cursor.c:630) ==50906== by 0x40F1C2A: cursor_update_outputs (wlr_cursor.c:657) ==50906== by 0x40F1CF9: wlr_cursor_set_xcursor (wlr_cursor.c:674) Fixes: 7963ba6a0deb5b696050d914ac395bca9c4c06b2 ("buffer: introduce wlr_buffer_finish()") --- render/allocator/shm.c | 1 + render/allocator/udmabuf.c | 1 + 2 files changed, 2 insertions(+) diff --git a/render/allocator/shm.c b/render/allocator/shm.c index 2622f99aa..b5be7d014 100644 --- a/render/allocator/shm.c +++ b/render/allocator/shm.c @@ -23,6 +23,7 @@ static struct wlr_shm_buffer *shm_buffer_from_buffer( static void buffer_destroy(struct wlr_buffer *wlr_buffer) { struct wlr_shm_buffer *buffer = shm_buffer_from_buffer(wlr_buffer); + wlr_buffer_finish(wlr_buffer); munmap(buffer->data, buffer->size); close(buffer->shm.fd); free(buffer); diff --git a/render/allocator/udmabuf.c b/render/allocator/udmabuf.c index e0b01b70a..8a7109aa5 100644 --- a/render/allocator/udmabuf.c +++ b/render/allocator/udmabuf.c @@ -31,6 +31,7 @@ static bool buffer_get_dmabuf(struct wlr_buffer *wlr_buffer, struct wlr_dmabuf_a static void buffer_destroy(struct wlr_buffer *wlr_buffer) { struct wlr_udmabuf_buffer *buffer = wl_container_of(wlr_buffer, buffer, base); + wlr_buffer_finish(wlr_buffer); wlr_dmabuf_attributes_finish(&buffer->dmabuf); close(buffer->shm.fd); free(buffer); From 9119b8aa851c413b79295f2a913124f0eed536d4 Mon Sep 17 00:00:00 2001 From: sunzhguy Date: Mon, 22 Dec 2025 10:36:11 +0800 Subject: [PATCH 196/311] session: simplify libudev unref handling Signed-off-by: sunzhguy --- backend/session/session.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/backend/session/session.c b/backend/session/session.c index 57b13b4ca..868774399 100644 --- a/backend/session/session.c +++ b/backend/session/session.c @@ -488,6 +488,7 @@ ssize_t wlr_session_find_gpus(struct wlr_session *session, if (udev_enumerate_get_list_entry(en) == NULL) { udev_enumerate_unref(en); + en = NULL; wlr_log(WLR_INFO, "Waiting for a KMS device"); struct find_gpus_add_handler handler = {0}; @@ -501,7 +502,6 @@ ssize_t wlr_session_find_gpus(struct wlr_session *session, if (ret < 0) { wlr_log_errno(WLR_ERROR, "Failed to wait for KMS device: " "wl_event_loop_dispatch failed"); - udev_enumerate_unref(en); return -1; } @@ -562,13 +562,11 @@ ssize_t wlr_session_find_gpus(struct wlr_session *session, struct wlr_device *wlr_dev = session_open_if_kms(session, udev_device_get_devnode(dev)); + udev_device_unref(dev); if (!wlr_dev) { - udev_device_unref(dev); continue; } - udev_device_unref(dev); - ret[i] = wlr_dev; if (is_primary) { struct wlr_device *tmp = ret[0]; From 8611aa8440072352ed3be0d2ef0688e79148ad38 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sun, 28 Dec 2025 22:30:25 +0100 Subject: [PATCH 197/311] scene: don't assign outputs to invisible nodes If a node has no visible areas, leave active_outputs empty. Fixes: 95b2771bfd09 ("scene: ignore outputs with too small intersection with nodes") --- types/scene/wlr_scene.c | 81 +++++++++++++++++++++-------------------- 1 file changed, 42 insertions(+), 39 deletions(-) diff --git a/types/scene/wlr_scene.c b/types/scene/wlr_scene.c index 07746f68c..335d5e31a 100644 --- a/types/scene/wlr_scene.c +++ b/types/scene/wlr_scene.c @@ -426,48 +426,50 @@ static void update_node_update_outputs(struct wlr_scene_node *node, size_t count = 0; uint64_t active_outputs = 0; - uint32_t visible_area = region_area(&node->visible); + if (!pixman_region32_empty(&node->visible)) { + uint32_t visible_area = region_area(&node->visible); - // let's update the outputs in two steps: - // - the primary outputs - // - the enter/leave signals - // This ensures that the enter/leave signals can rely on the primary output - // to have a reasonable value. Otherwise, they may get a value that's in - // the middle of a calculation. - struct wlr_scene_output *scene_output; - wl_list_for_each(scene_output, outputs, link) { - if (scene_output == ignore) { - continue; - } - - if (!scene_output->output->enabled) { - continue; - } - - struct wlr_box output_box = { - .x = scene_output->x, - .y = scene_output->y, - }; - wlr_output_effective_resolution(scene_output->output, - &output_box.width, &output_box.height); - - pixman_region32_t intersection; - pixman_region32_init(&intersection); - pixman_region32_intersect_rect(&intersection, &node->visible, - output_box.x, output_box.y, output_box.width, output_box.height); - uint32_t overlap = region_area(&intersection); - pixman_region32_fini(&intersection); - - // If the overlap accounts for less than 10% of the visible node area, - // ignore this output - if (overlap >= 0.1 * visible_area) { - if (overlap >= largest_overlap) { - largest_overlap = overlap; - scene_buffer->primary_output = scene_output; + // let's update the outputs in two steps: + // - the primary outputs + // - the enter/leave signals + // This ensures that the enter/leave signals can rely on the primary output + // to have a reasonable value. Otherwise, they may get a value that's in + // the middle of a calculation. + struct wlr_scene_output *scene_output; + wl_list_for_each(scene_output, outputs, link) { + if (scene_output == ignore) { + continue; } - active_outputs |= 1ull << scene_output->index; - count++; + if (!scene_output->output->enabled) { + continue; + } + + struct wlr_box output_box = { + .x = scene_output->x, + .y = scene_output->y, + }; + wlr_output_effective_resolution(scene_output->output, + &output_box.width, &output_box.height); + + pixman_region32_t intersection; + pixman_region32_init(&intersection); + pixman_region32_intersect_rect(&intersection, &node->visible, + output_box.x, output_box.y, output_box.width, output_box.height); + uint32_t overlap = region_area(&intersection); + pixman_region32_fini(&intersection); + + // If the overlap accounts for less than 10% of the visible node area, + // ignore this output + if (overlap >= 0.1 * visible_area) { + if (overlap >= largest_overlap) { + largest_overlap = overlap; + scene_buffer->primary_output = scene_output; + } + + active_outputs |= 1ull << scene_output->index; + count++; + } } } @@ -479,6 +481,7 @@ static void update_node_update_outputs(struct wlr_scene_node *node, uint64_t old_active = scene_buffer->active_outputs; scene_buffer->active_outputs = active_outputs; + struct wlr_scene_output *scene_output; wl_list_for_each(scene_output, outputs, link) { uint64_t mask = 1ull << scene_output->index; bool intersects = active_outputs & mask; From 53cdceb371f31be8e58705ac53bee375283bbb91 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sun, 28 Dec 2025 22:56:55 +0100 Subject: [PATCH 198/311] scene: constify pixman_region32_t Makes it easier to figure out which functions build regions, and which functions consume them. --- types/scene/wlr_scene.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/types/scene/wlr_scene.c b/types/scene/wlr_scene.c index 335d5e31a..0634f62e2 100644 --- a/types/scene/wlr_scene.c +++ b/types/scene/wlr_scene.c @@ -289,7 +289,7 @@ static void scene_node_opaque_region(struct wlr_scene_node *node, int x, int y, struct scene_update_data { pixman_region32_t *visible; - pixman_region32_t *update_region; + const pixman_region32_t *update_region; struct wlr_box update_box; struct wl_list *outputs; bool calculate_visibility; @@ -299,7 +299,7 @@ struct scene_update_data { #endif }; -static uint32_t region_area(pixman_region32_t *region) { +static uint32_t region_area(const pixman_region32_t *region) { uint32_t area = 0; int nrects; @@ -391,7 +391,7 @@ static void scene_output_damage_whole(struct wlr_scene_output *scene_output) { pixman_region32_fini(&damage); } -static void scene_damage_outputs(struct wlr_scene *scene, pixman_region32_t *damage) { +static void scene_damage_outputs(struct wlr_scene *scene, const pixman_region32_t *damage) { if (pixman_region32_empty(damage)) { return; } @@ -654,7 +654,7 @@ static void scene_node_bounds(struct wlr_scene_node *node, } static void scene_update_region(struct wlr_scene *scene, - pixman_region32_t *update_region) { + const pixman_region32_t *update_region) { pixman_region32_t visible; pixman_region32_init(&visible); pixman_region32_copy(&visible, update_region); From d512c00791e64b093a0d6fce1bac85c00f01c9e9 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Thu, 20 Nov 2025 18:25:35 +0100 Subject: [PATCH 199/311] render: add new 16- and 32-bits-per-component pixel formats These new formats have been introduced in libdrm 2.4.129: https://gitlab.freedesktop.org/mesa/libdrm/-/commit/31e68ea81c33bfd08fc6c8973fae60338f963f90 --- meson.build | 2 +- render/pixel_format.c | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 4b24e5046..e602bb427 100644 --- a/meson.build +++ b/meson.build @@ -98,7 +98,7 @@ wayland_server = dependency('wayland-server', ) drm = dependency('libdrm', - version: '>=2.4.122', + version: '>=2.4.129', fallback: 'libdrm', default_options: [ 'auto_features=disabled', diff --git a/render/pixel_format.c b/render/pixel_format.c index 7befbf082..5f2e8644a 100644 --- a/render/pixel_format.c +++ b/render/pixel_format.c @@ -44,10 +44,26 @@ static const struct wlr_pixel_format_info pixel_format_info[] = { .drm_format = DRM_FORMAT_R8, .bytes_per_block = 1, }, + { + .drm_format = DRM_FORMAT_R16F, + .bytes_per_block = 2, + }, + { + .drm_format = DRM_FORMAT_R32F, + .bytes_per_block = 4, + }, { .drm_format = DRM_FORMAT_GR88, .bytes_per_block = 2, }, + { + .drm_format = DRM_FORMAT_GR1616F, + .bytes_per_block = 4, + }, + { + .drm_format = DRM_FORMAT_GR3232F, + .bytes_per_block = 8, + }, { .drm_format = DRM_FORMAT_RGB888, .bytes_per_block = 3, @@ -56,6 +72,18 @@ static const struct wlr_pixel_format_info pixel_format_info[] = { .drm_format = DRM_FORMAT_BGR888, .bytes_per_block = 3, }, + { + .drm_format = DRM_FORMAT_BGR161616, + .bytes_per_block = 6, + }, + { + .drm_format = DRM_FORMAT_BGR161616F, + .bytes_per_block = 6, + }, + { + .drm_format = DRM_FORMAT_BGR323232F, + .bytes_per_block = 12, + }, { .drm_format = DRM_FORMAT_RGBX4444, .bytes_per_block = 2, @@ -136,6 +164,10 @@ static const struct wlr_pixel_format_info pixel_format_info[] = { .opaque_substitute = DRM_FORMAT_XBGR16161616F, .bytes_per_block = 8, }, + { + .drm_format = DRM_FORMAT_ABGR32323232F, + .bytes_per_block = 16, + }, { .drm_format = DRM_FORMAT_XBGR16161616, .bytes_per_block = 8, From 1723f851d249c997d3ce32b1fa671eb04645b501 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Thu, 20 Nov 2025 18:27:19 +0100 Subject: [PATCH 200/311] render/gles2: add BGR161616F and BGR161616 --- render/gles2/pixel_format.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/render/gles2/pixel_format.c b/render/gles2/pixel_format.c index e74cb29fa..fbc113817 100644 --- a/render/gles2/pixel_format.c +++ b/render/gles2/pixel_format.c @@ -70,6 +70,11 @@ static const struct wlr_gles2_pixel_format formats[] = { .gl_format = GL_RGBA, .gl_type = GL_UNSIGNED_INT_2_10_10_10_REV_EXT, }, + { + .drm_format = DRM_FORMAT_BGR161616F, + .gl_format = GL_RGB, + .gl_type = GL_HALF_FLOAT_OES, + }, { .drm_format = DRM_FORMAT_XBGR16161616F, .gl_format = GL_RGBA, @@ -80,6 +85,12 @@ static const struct wlr_gles2_pixel_format formats[] = { .gl_format = GL_RGBA, .gl_type = GL_HALF_FLOAT_OES, }, + { + .drm_format = DRM_FORMAT_BGR161616, + .gl_internalformat = GL_RGB16_EXT, + .gl_format = GL_RGB, + .gl_type = GL_UNSIGNED_SHORT, + }, { .drm_format = DRM_FORMAT_XBGR16161616, .gl_internalformat = GL_RGBA16_EXT, From ca8b49d8581b3b1f5a63d2f95007279ca40efd6b Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Thu, 20 Nov 2025 18:27:54 +0100 Subject: [PATCH 201/311] render/vulkan: add new 16- and 32-bits-per-component pixel formats Compatibility table based on pixfmtdb. --- render/vulkan/pixel_format.c | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/render/vulkan/pixel_format.c b/render/vulkan/pixel_format.c index 9319d0daa..902feac9a 100644 --- a/render/vulkan/pixel_format.c +++ b/render/vulkan/pixel_format.c @@ -17,11 +17,27 @@ static const struct wlr_vk_format formats[] = { .vk = VK_FORMAT_R8_UNORM, .vk_srgb = VK_FORMAT_R8_SRGB, }, + { + .drm = DRM_FORMAT_R16F, + .vk = VK_FORMAT_R16_SFLOAT, + }, + { + .drm = DRM_FORMAT_R32F, + .vk = VK_FORMAT_R32_SFLOAT, + }, { .drm = DRM_FORMAT_GR88, .vk = VK_FORMAT_R8G8_UNORM, .vk_srgb = VK_FORMAT_R8G8_SRGB, }, + { + .drm = DRM_FORMAT_GR1616F, + .vk = VK_FORMAT_R16G16_SFLOAT, + }, + { + .drm = DRM_FORMAT_GR3232F, + .vk = VK_FORMAT_R32G32_SFLOAT, + }, { .drm = DRM_FORMAT_RGB888, .vk = VK_FORMAT_B8G8R8_UNORM, @@ -126,6 +142,14 @@ static const struct wlr_vk_format formats[] = { // On little endian systems the memory representation of each channel // matches the DRM formats'. #if WLR_LITTLE_ENDIAN + { + .drm = DRM_FORMAT_BGR161616, + .vk = VK_FORMAT_R16G16B16_UNORM, + }, + { + .drm = DRM_FORMAT_BGR161616F, + .vk = VK_FORMAT_R16G16B16_SFLOAT, + }, { .drm = DRM_FORMAT_ABGR16161616, .vk = VK_FORMAT_R16G16B16A16_UNORM, @@ -142,6 +166,14 @@ static const struct wlr_vk_format formats[] = { .drm = DRM_FORMAT_XBGR16161616F, .vk = VK_FORMAT_R16G16B16A16_SFLOAT, }, + { + .drm = DRM_FORMAT_BGR323232F, + .vk = VK_FORMAT_R32G32B32_SFLOAT, + }, + { + .drm = DRM_FORMAT_ABGR32323232F, + .vk = VK_FORMAT_R32G32B32A32_SFLOAT, + }, #endif // YCbCr formats From 3f9a16448403f57bb7a562ed351f26a0e4206b77 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sat, 3 Jan 2026 23:16:56 +0100 Subject: [PATCH 202/311] scene: keep last preferred configuration when leaving last output Before this patch, when a surface became occluded on all outputs, we'd reconfigure it with a base configuration (scale set to 1, transform set to NORMAL, image description set to gamma 2.2/sRGB). As a result, when quickly hiding a toplevel and showing it again, the client would render to switch to the base configuration, then render again to switch to the output configuration. Avoi this needless back-and-forth by retaining the last sent preferred configuration when a surface leaves all outputs. --- types/scene/surface.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/types/scene/surface.c b/types/scene/surface.c index 0c24208cb..dadbf8303 100644 --- a/types/scene/surface.c +++ b/types/scene/surface.c @@ -96,6 +96,12 @@ static void handle_scene_buffer_outputs_update( surface->frame_pacing_output = get_surface_frame_pacing_output(surface->surface); + // If the surface is no longer visible on any output, keep the last sent + // preferred configuration to avoid unnecessary redraws + if (wl_list_empty(&surface->surface->current_outputs)) { + return; + } + double scale = get_surface_preferred_buffer_scale(surface->surface); wlr_fractional_scale_v1_notify_scale(surface->surface, scale); wlr_surface_set_preferred_buffer_scale(surface->surface, ceil(scale)); From caed9d78d7efacd3a597ef08e8644c45cc34726f Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Thu, 1 Jan 2026 19:34:06 +0100 Subject: [PATCH 203/311] render: drop compat defines It's been 3 years, so these defines are widespread enough by now. --- render/dmabuf_linux.c | 24 ------------------------ render/meson.build | 6 +++++- 2 files changed, 5 insertions(+), 25 deletions(-) diff --git a/render/dmabuf_linux.c b/render/dmabuf_linux.c index cf06229c5..53fb909bc 100644 --- a/render/dmabuf_linux.c +++ b/render/dmabuf_linux.c @@ -50,30 +50,6 @@ bool dmabuf_check_sync_file_import_export(void) { return KERNEL_VERSION(major, minor, patch) >= KERNEL_VERSION(5, 20, 0); } -// TODO: drop these definitions once widespread - -#if !defined(DMA_BUF_IOCTL_IMPORT_SYNC_FILE) - -struct dma_buf_import_sync_file { - __u32 flags; - __s32 fd; -}; - -#define DMA_BUF_IOCTL_IMPORT_SYNC_FILE _IOW(DMA_BUF_BASE, 3, struct dma_buf_import_sync_file) - -#endif - -#if !defined(DMA_BUF_IOCTL_EXPORT_SYNC_FILE) - -struct dma_buf_export_sync_file { - __u32 flags; - __s32 fd; -}; - -#define DMA_BUF_IOCTL_EXPORT_SYNC_FILE _IOWR(DMA_BUF_BASE, 2, struct dma_buf_export_sync_file) - -#endif - bool dmabuf_import_sync_file(int dmabuf_fd, uint32_t flags, int sync_file_fd) { struct dma_buf_import_sync_file data = { .flags = flags, diff --git a/render/meson.build b/render/meson.build index 7c1254078..aaaf2ec48 100644 --- a/render/meson.build +++ b/render/meson.build @@ -17,7 +17,11 @@ wlr_files += files( 'wlr_texture.c', ) -if cc.has_header('linux/dma-buf.h') and target_machine.system() == 'linux' +has_dma_buf_import_sync_file = cc.has_header('linux/dma-buf.h') and cc.has_define( + 'DMA_BUF_IOCTL_IMPORT_SYNC_FILE', + prefix: '#include ', +) +if has_dma_buf_import_sync_file and target_machine.system() == 'linux' wlr_files += files('dmabuf_linux.c') else wlr_files += files('dmabuf_fallback.c') From 2699b68b34520a7efd0927ec9f186ecb39488946 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sun, 28 Dec 2025 20:12:04 +0100 Subject: [PATCH 204/311] ext_image_capture_source_v1/scene: fix stop for parallel captures The stop handler disables the output. However, the same source can be captured multiple times in parallel. In that case, stop might be called while another capture session is still ongoing. Only disable the output if the last session is stopped. --- types/ext_image_capture_source_v1/scene.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/types/ext_image_capture_source_v1/scene.c b/types/ext_image_capture_source_v1/scene.c index a8bce9d3d..b1e46dcad 100644 --- a/types/ext_image_capture_source_v1/scene.c +++ b/types/ext_image_capture_source_v1/scene.c @@ -20,6 +20,7 @@ struct scene_node_source { struct wlr_scene_output *scene_output; struct wl_event_source *idle_frame; + size_t num_started; struct wl_listener node_destroy; struct wl_listener scene_output_destroy; @@ -103,12 +104,23 @@ static void source_render(struct scene_node_source *source) { static void source_start(struct wlr_ext_image_capture_source_v1 *base, bool with_cursors) { struct scene_node_source *source = wl_container_of(base, source, base); + + source->num_started++; + if (source->num_started > 1) { + return; + } + source_render(source); } static void source_stop(struct wlr_ext_image_capture_source_v1 *base) { struct scene_node_source *source = wl_container_of(base, source, base); + source->num_started--; + if (source->num_started > 0) { + return; + } + struct wlr_output_state state; wlr_output_state_init(&state); wlr_output_state_set_enabled(&state, false); From b094f8aeb3ce207cc1e158787f4a5338cced2beb Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Tue, 30 Dec 2025 20:13:23 +0100 Subject: [PATCH 205/311] scene/surface: don't cache frame pacing output Storing the frame pacing output in a per-scene and per-surface struct doesn't play well with multiple scenes. outputs_update is only triggered for outputs the scene knows about, but operates on all outputs the surface has entered regardless of the scene. Thus leaving an output on one scene will not refresh the frame pacing output on other scenes, and these other scenes will operate with a stale frame pacing output. The surface will not receive any more wl_surface.frame done events. This also avoids keeping a dangling pointer around when the frame pacing output is destroyed but the output isn't added in the scene. References: https://github.com/swaywm/sway/issues/8885 --- include/wlr/types/wlr_scene.h | 4 ---- types/scene/surface.c | 8 ++++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/include/wlr/types/wlr_scene.h b/include/wlr/types/wlr_scene.h index 2363c93ce..6fd243477 100644 --- a/include/wlr/types/wlr_scene.h +++ b/include/wlr/types/wlr_scene.h @@ -126,10 +126,6 @@ struct wlr_scene_surface { struct { struct wlr_box clip; - // Output used for frame pacing (surface frame callbacks, presentation - // time feedback, etc), may be NULL - struct wlr_output *frame_pacing_output; - struct wlr_addon addon; struct wl_listener outputs_update; diff --git a/types/scene/surface.c b/types/scene/surface.c index dadbf8303..bce8c74a6 100644 --- a/types/scene/surface.c +++ b/types/scene/surface.c @@ -24,6 +24,8 @@ static double get_surface_preferred_buffer_scale(struct wlr_surface *surface) { return scale; } +// Output used for frame pacing (surface frame callbacks, presentation +// time feedback, etc), may be NULL static struct wlr_output *get_surface_frame_pacing_output(struct wlr_surface *surface) { struct wlr_output *frame_pacing_output = NULL; struct wlr_surface_output *surface_output; @@ -94,8 +96,6 @@ static void handle_scene_buffer_outputs_update( wl_container_of(listener, surface, outputs_update); struct wlr_scene *scene = scene_node_get_root(&surface->buffer->node); - surface->frame_pacing_output = get_surface_frame_pacing_output(surface->surface); - // If the surface is no longer visible on any output, keep the last sent // preferred configuration to avoid unnecessary redraws if (wl_list_empty(&surface->surface->current_outputs)) { @@ -138,7 +138,7 @@ static void handle_scene_buffer_output_sample( wl_container_of(listener, surface, output_sample); const struct wlr_scene_output_sample_event *event = data; struct wlr_output *output = event->output->output; - if (surface->frame_pacing_output != output) { + if (get_surface_frame_pacing_output(surface->surface) != output) { return; } @@ -154,7 +154,7 @@ static void handle_scene_buffer_frame_done( struct wlr_scene_surface *surface = wl_container_of(listener, surface, frame_done); struct wlr_scene_frame_done_event *event = data; - if (surface->frame_pacing_output != event->output->output) { + if (get_surface_frame_pacing_output(surface->surface) != event->output->output) { return; } From 2c64b30a6750d5e585c00c4c116f415bac33d18f Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Tue, 8 Jul 2025 15:00:57 +0200 Subject: [PATCH 206/311] scene: add knob to turn off Xwayland surface restacking This is useful in these cases: - The same surface is added to two different scene-graphs. wlroots can't figure out on its own which scene-graph should drive the Xwayland stacking. - A compositor uses multiple Xwayland servers (e.g. one per app). wlroots will try to restack surfaces from different Xwayland instances and this will not go well. --- include/wlr/types/wlr_scene.h | 2 ++ types/scene/wlr_scene.c | 12 ++++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/include/wlr/types/wlr_scene.h b/include/wlr/types/wlr_scene.h index 6fd243477..46635f4bf 100644 --- a/include/wlr/types/wlr_scene.h +++ b/include/wlr/types/wlr_scene.h @@ -105,6 +105,8 @@ struct wlr_scene { struct wlr_gamma_control_manager_v1 *gamma_control_manager_v1; struct wlr_color_manager_v1 *color_manager_v1; + bool restack_xwayland_surfaces; + struct { struct wl_listener linux_dmabuf_v1_destroy; struct wl_listener gamma_control_manager_v1_destroy; diff --git a/types/scene/wlr_scene.c b/types/scene/wlr_scene.c index 0634f62e2..16a748e79 100644 --- a/types/scene/wlr_scene.c +++ b/types/scene/wlr_scene.c @@ -185,6 +185,8 @@ struct wlr_scene *wlr_scene_create(void) { wl_list_init(&scene->gamma_control_manager_v1_destroy.link); wl_list_init(&scene->gamma_control_manager_v1_set_gamma.link); + scene->restack_xwayland_surfaces = true; + const char *debug_damage_options[] = { "none", "rerender", @@ -293,6 +295,7 @@ struct scene_update_data { struct wlr_box update_box; struct wl_list *outputs; bool calculate_visibility; + bool restack_xwayland_surfaces; #if WLR_HAS_XWAYLAND struct wlr_xwayland_surface *restack_above; @@ -609,7 +612,9 @@ static bool scene_node_update_iterator(struct wlr_scene_node *node, update_node_update_outputs(node, data->outputs, NULL, NULL); #if WLR_HAS_XWAYLAND - restack_xwayland_surface(node, &box, data); + if (data->restack_xwayland_surfaces) { + restack_xwayland_surface(node, &box, data); + } #endif return false; @@ -671,6 +676,7 @@ static void scene_update_region(struct wlr_scene *scene, }, .outputs = &scene->outputs, .calculate_visibility = scene->calculate_visibility, + .restack_xwayland_surfaces = scene->restack_xwayland_surfaces, }; // update node visibility and output enter/leave events @@ -686,7 +692,9 @@ static void scene_node_update(struct wlr_scene_node *node, int x, y; if (!wlr_scene_node_coords(node, &x, &y)) { #if WLR_HAS_XWAYLAND - restack_xwayland_surface_below(node); + if (scene->restack_xwayland_surfaces) { + restack_xwayland_surface_below(node); + } #endif if (damage) { scene_update_region(scene, damage); From 89c9ef6692fef7672dadd01c7a2326c1b94d35ce Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sat, 3 Jan 2026 19:35:17 +0100 Subject: [PATCH 207/311] render/gles2: skip glslang check when shaders are unchanged Currently, the glslang check is run every time ninja is invoked, even with an up-to-date build directory when GLSL shaders haven't been modified. This is due to glslang not creating any output file: the _check file never exists so ninja keeps trying to generate it by running the command. Unfortunately Meson doesn't support running commands with no outputs [1]. Create an empty output file to fix this by setting `capture: true`. This doesn't work out-of-the-box, because glslang prints messages to stdout, and provides no way to change this [2]. As a result, shader errors are not surfaced back to the user - they end up in the _check file. Workaround this with a thin wrapper which redirects stdout to stderr when invoking glslang. [1]: https://github.com/mesonbuild/meson/issues/11506 [2]: https://github.com/KhronosGroup/glslang/pull/4138 --- render/gles2/shaders/check.sh | 4 ++++ render/gles2/shaders/meson.build | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 render/gles2/shaders/check.sh diff --git a/render/gles2/shaders/check.sh b/render/gles2/shaders/check.sh new file mode 100644 index 000000000..18f4be246 --- /dev/null +++ b/render/gles2/shaders/check.sh @@ -0,0 +1,4 @@ +#!/bin/sh -eu + +# glslang prints log messages to stdout, remap to stderr +exec "$@" >&2 diff --git a/render/gles2/shaders/meson.build b/render/gles2/shaders/meson.build index 64e4e93fb..1e649f7b5 100644 --- a/render/gles2/shaders/meson.build +++ b/render/gles2/shaders/meson.build @@ -1,3 +1,4 @@ +check = find_program('./check.sh', native: true) embed = find_program('./embed.sh', native: true) shaders = [ @@ -13,7 +14,8 @@ foreach name : shaders 'gles2-' + name, input: name, output: name + '_check', - command: [glslang, '@INPUT@'], + command: [check, glslang, '@INPUT@'], + capture: true, build_by_default: true, ) From 98733c91b450728392af63bafbb45460d46c9be1 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sun, 4 Jan 2026 18:17:19 +0100 Subject: [PATCH 208/311] xcursor: add shared helper to create a wlr_xcursor_image This logic was duplicated in two spots. --- xcursor/wlr_xcursor.c | 68 ++++++++++++++++++++----------------------- 1 file changed, 31 insertions(+), 37 deletions(-) diff --git a/xcursor/wlr_xcursor.c b/xcursor/wlr_xcursor.c index 33729f1f3..163ba3dec 100644 --- a/xcursor/wlr_xcursor.c +++ b/xcursor/wlr_xcursor.c @@ -31,6 +31,8 @@ #include #include "xcursor/xcursor.h" +#include "xcursor/cursor_data.h" + static void xcursor_destroy(struct wlr_xcursor *cursor) { for (size_t i = 0; i < cursor->image_count; i++) { free(cursor->images[i]->buffer); @@ -42,7 +44,30 @@ static void xcursor_destroy(struct wlr_xcursor *cursor) { free(cursor); } -#include "xcursor/cursor_data.h" +static struct wlr_xcursor_image *xcursor_image_create(uint32_t width, uint32_t height, + uint32_t hotspot_x, uint32_t hotspot_y, uint32_t delay, const void *buffer) { + struct wlr_xcursor_image *image = calloc(1, sizeof(*image)); + if (image == NULL) { + return NULL; + } + + image->width = width; + image->height = height; + image->hotspot_x = hotspot_x; + image->hotspot_y = hotspot_y; + image->delay = delay; + + size_t size = width * height * sizeof(uint32_t); + image->buffer = malloc(size); + if (image->buffer == NULL) { + free(image); + return NULL; + } + + memcpy(image->buffer, buffer, size); + + return image; +} static struct wlr_xcursor *xcursor_create_from_data( const struct cursor_metadata *metadata, struct wlr_xcursor_theme *theme) { @@ -60,32 +85,16 @@ static struct wlr_xcursor *xcursor_create_from_data( cursor->name = strdup(metadata->name); cursor->total_delay = 0; - struct wlr_xcursor_image *image = calloc(1, sizeof(*image)); + struct wlr_xcursor_image *image = xcursor_image_create(metadata->width, metadata->height, + metadata->hotspot_x, metadata->hotspot_y, 0, cursor_data + metadata->offset); if (!image) { goto err_free_images; } cursor->images[0] = image; - image->buffer = NULL; - image->width = metadata->width; - image->height = metadata->height; - image->hotspot_x = metadata->hotspot_x; - image->hotspot_y = metadata->hotspot_y; - image->delay = 0; - - int size = metadata->width * metadata->height * sizeof(uint32_t); - image->buffer = malloc(size); - if (!image->buffer) { - goto err_free_image; - } - - memcpy(image->buffer, cursor_data + metadata->offset, size); return cursor; -err_free_image: - free(image); - err_free_images: free(cursor->name); free(cursor->images); @@ -132,28 +141,13 @@ static struct wlr_xcursor *xcursor_create_from_xcursor_images( cursor->total_delay = 0; for (int i = 0; i < images->nimage; i++) { - struct wlr_xcursor_image *image = calloc(1, sizeof(*image)); + const struct xcursor_image *data = images->images[i]; + struct wlr_xcursor_image *image = xcursor_image_create(data->width, data->height, + data->xhot, data->yhot, data->delay, data->pixels); if (image == NULL) { break; } - image->buffer = NULL; - - image->width = images->images[i]->width; - image->height = images->images[i]->height; - image->hotspot_x = images->images[i]->xhot; - image->hotspot_y = images->images[i]->yhot; - image->delay = images->images[i]->delay; - - size_t size = image->width * image->height * 4; - image->buffer = malloc(size); - if (!image->buffer) { - free(image); - break; - } - - /* copy pixels to shm pool */ - memcpy(image->buffer, images->images[i]->pixels, size); cursor->total_delay += image->delay; cursor->images[i] = image; cursor->image_count++; From d7f7b68f494eadbe2245155f0ecdb6ab3501c1e7 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sun, 4 Jan 2026 18:24:18 +0100 Subject: [PATCH 209/311] xcursor: introduce wlr_xcursor_image_get_buffer() This makes it so callers no longer need to juggle with raw pixel pointers anymore, and only need a single wlr_buffer-based codepath. Additionally, cursors can be unloaded without risking use-after-free. --- include/wlr/xcursor.h | 9 +++++++++ xcursor/wlr_xcursor.c | 25 ++++++++++++++++++++++--- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/include/wlr/xcursor.h b/include/wlr/xcursor.h index bb75119b5..7cf333d70 100644 --- a/include/wlr/xcursor.h +++ b/include/wlr/xcursor.h @@ -56,6 +56,10 @@ struct wlr_xcursor_image { uint32_t hotspot_y; /* hot-spot y (must be inside image) */ uint32_t delay; /* animation delay to next frame (ms) */ uint8_t *buffer; /* pixel data */ + + struct { + struct wlr_readonly_data_buffer *readonly_buffer; + } WLR_PRIVATE; }; /** @@ -119,6 +123,11 @@ struct wlr_xcursor *wlr_xcursor_theme_get_cursor( */ int wlr_xcursor_frame(struct wlr_xcursor *cursor, uint32_t time); +/** + * Get a struct wlr_buffer from a cursor image. + */ +struct wlr_buffer *wlr_xcursor_image_get_buffer(struct wlr_xcursor_image *image); + /** * Get the name of the resize cursor for the given edges. */ diff --git a/xcursor/wlr_xcursor.c b/xcursor/wlr_xcursor.c index 163ba3dec..3b4d25cff 100644 --- a/xcursor/wlr_xcursor.c +++ b/xcursor/wlr_xcursor.c @@ -23,18 +23,21 @@ * SOFTWARE. */ +#include #include #include #include #include #include #include +#include "types/wlr_buffer.h" #include "xcursor/xcursor.h" #include "xcursor/cursor_data.h" static void xcursor_destroy(struct wlr_xcursor *cursor) { for (size_t i = 0; i < cursor->image_count; i++) { + readonly_data_buffer_drop(cursor->images[i]->readonly_buffer); free(cursor->images[i]->buffer); free(cursor->images[i]); } @@ -57,16 +60,28 @@ static struct wlr_xcursor_image *xcursor_image_create(uint32_t width, uint32_t h image->hotspot_y = hotspot_y; image->delay = delay; - size_t size = width * height * sizeof(uint32_t); + size_t stride = width * sizeof(uint32_t); + size_t size = stride * height; image->buffer = malloc(size); if (image->buffer == NULL) { - free(image); - return NULL; + goto err_image; } memcpy(image->buffer, buffer, size); + image->readonly_buffer = readonly_data_buffer_create(DRM_FORMAT_ARGB8888, + stride, width, height, image->buffer); + if (image->readonly_buffer == NULL) { + goto err_buffer; + } + return image; + +err_buffer: + free(image->buffer); +err_image: + free(image); + return NULL; } static struct wlr_xcursor *xcursor_create_from_data( @@ -330,6 +345,10 @@ int wlr_xcursor_frame(struct wlr_xcursor *_cursor, uint32_t time) { return xcursor_frame_and_duration(_cursor, time, NULL); } +struct wlr_buffer *wlr_xcursor_image_get_buffer(struct wlr_xcursor_image *image) { + return &image->readonly_buffer->base; +} + const char *wlr_xcursor_get_resize_name(enum wlr_edges edges) { if (edges & WLR_EDGE_TOP) { if (edges & WLR_EDGE_RIGHT) { From 53d75a41c73a4cc1d4acce91e94745c51a341a96 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sun, 4 Jan 2026 18:29:33 +0100 Subject: [PATCH 210/311] cursor: use wlr_xcursor_image_get_buffer() --- types/wlr_cursor.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/types/wlr_cursor.c b/types/wlr_cursor.c index 7895cc4ed..f2269db0f 100644 --- a/types/wlr_cursor.c +++ b/types/wlr_cursor.c @@ -17,7 +17,6 @@ #include #include #include -#include "types/wlr_buffer.h" #include "types/wlr_output.h" struct wlr_cursor_device { @@ -499,13 +498,8 @@ static int handle_xcursor_timer(void *data) { static void output_cursor_set_xcursor_image(struct wlr_cursor_output_cursor *output_cursor, size_t i) { struct wlr_xcursor_image *image = output_cursor->xcursor->images[i]; - struct wlr_readonly_data_buffer *ro_buffer = readonly_data_buffer_create( - DRM_FORMAT_ARGB8888, 4 * image->width, image->width, image->height, image->buffer); - if (ro_buffer == NULL) { - return; - } - wlr_output_cursor_set_buffer(output_cursor->output_cursor, &ro_buffer->base, image->hotspot_x, image->hotspot_y); - wlr_buffer_drop(&ro_buffer->base); + struct wlr_buffer *buffer = wlr_xcursor_image_get_buffer(image); + wlr_output_cursor_set_buffer(output_cursor->output_cursor, buffer, image->hotspot_x, image->hotspot_y); output_cursor->xcursor_index = i; From 84d603acc06a45dd3c3a4b2cf1fd08b2933ca2b5 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sun, 4 Jan 2026 18:43:12 +0100 Subject: [PATCH 211/311] xwayland: take wlr_buffer in wlr_xwayland_set_cursor() --- include/wlr/xwayland/xwayland.h | 10 +++++--- include/xwayland/xwm.h | 4 ++-- xwayland/xwayland.c | 41 +++++++++------------------------ xwayland/xwm.c | 27 ++++++++++++++++++---- 4 files changed, 42 insertions(+), 40 deletions(-) diff --git a/include/wlr/xwayland/xwayland.h b/include/wlr/xwayland/xwayland.h index 1c52b3558..c3704aad1 100644 --- a/include/wlr/xwayland/xwayland.h +++ b/include/wlr/xwayland/xwayland.h @@ -17,6 +17,7 @@ #include struct wlr_box; +struct wlr_buffer; struct wlr_xwm; struct wlr_data_source; struct wlr_drag; @@ -41,7 +42,6 @@ struct wlr_xwayland { bool own_server; struct wlr_xwm *xwm; struct wlr_xwayland_shell_v1 *shell_v1; - struct wlr_xwayland_cursor *cursor; // Value the DISPLAY environment variable should be set to by the compositor const char *display_name; @@ -67,6 +67,11 @@ struct wlr_xwayland { void *data; struct { + struct wlr_buffer *cursor_buffer; + struct { + int32_t x, y; + } cursor_hotspot; + struct wl_listener server_start; struct wl_listener server_ready; struct wl_listener server_destroy; @@ -279,8 +284,7 @@ struct wlr_xwayland *wlr_xwayland_create_with_server(struct wl_display *display, void wlr_xwayland_destroy(struct wlr_xwayland *wlr_xwayland); void wlr_xwayland_set_cursor(struct wlr_xwayland *wlr_xwayland, - uint8_t *pixels, uint32_t stride, uint32_t width, uint32_t height, - int32_t hotspot_x, int32_t hotspot_y); + struct wlr_buffer *buffer, int32_t hotspot_x, int32_t hotspot_y); void wlr_xwayland_surface_activate(struct wlr_xwayland_surface *surface, bool activated); diff --git a/include/xwayland/xwm.h b/include/xwayland/xwm.h index 73f440d29..302518d39 100644 --- a/include/xwayland/xwm.h +++ b/include/xwayland/xwm.h @@ -169,8 +169,8 @@ struct wlr_xwm *xwm_create(struct wlr_xwayland *wlr_xwayland, int wm_fd); void xwm_destroy(struct wlr_xwm *xwm); -void xwm_set_cursor(struct wlr_xwm *xwm, const uint8_t *pixels, uint32_t stride, - uint32_t width, uint32_t height, int32_t hotspot_x, int32_t hotspot_y); +void xwm_set_cursor(struct wlr_xwm *xwm, struct wlr_buffer *buffer, + int32_t hotspot_x, int32_t hotspot_y); int xwm_handle_selection_event(struct wlr_xwm *xwm, xcb_generic_event_t *event); int xwm_handle_selection_client_message(struct wlr_xwm *xwm, diff --git a/xwayland/xwayland.c b/xwayland/xwayland.c index 3aa47bac2..39ca822dc 100644 --- a/xwayland/xwayland.c +++ b/xwayland/xwayland.c @@ -8,21 +8,13 @@ #include #include #include +#include #include #include #include #include #include "xwayland/xwm.h" -struct wlr_xwayland_cursor { - uint8_t *pixels; - uint32_t stride; - uint32_t width; - uint32_t height; - int32_t hotspot_x; - int32_t hotspot_y; -}; - static void handle_server_destroy(struct wl_listener *listener, void *data) { struct wlr_xwayland *xwayland = wl_container_of(listener, xwayland, server_destroy); @@ -53,10 +45,9 @@ static void xwayland_mark_ready(struct wlr_xwayland *xwayland) { xwm_set_seat(xwayland->xwm, xwayland->seat); } - if (xwayland->cursor != NULL) { - struct wlr_xwayland_cursor *cur = xwayland->cursor; - xwm_set_cursor(xwayland->xwm, cur->pixels, cur->stride, cur->width, - cur->height, cur->hotspot_x, cur->hotspot_y); + if (xwayland->cursor_buffer != NULL) { + xwm_set_cursor(xwayland->xwm, xwayland->cursor_buffer, + xwayland->cursor_hotspot.x, xwayland->cursor_hotspot.y); } wl_signal_emit_mutable(&xwayland->events.ready, NULL); @@ -95,7 +86,7 @@ void wlr_xwayland_destroy(struct wlr_xwayland *xwayland) { wl_list_remove(&xwayland->server_start.link); wl_list_remove(&xwayland->server_ready.link); wl_list_remove(&xwayland->shell_destroy.link); - free(xwayland->cursor); + wlr_buffer_unlock(xwayland->cursor_buffer); wlr_xwayland_set_seat(xwayland, NULL); if (xwayland->own_server) { @@ -181,26 +172,16 @@ error_shell_v1: } void wlr_xwayland_set_cursor(struct wlr_xwayland *xwayland, - uint8_t *pixels, uint32_t stride, uint32_t width, uint32_t height, - int32_t hotspot_x, int32_t hotspot_y) { + struct wlr_buffer *buffer, int32_t hotspot_x, int32_t hotspot_y) { if (xwayland->xwm != NULL) { - xwm_set_cursor(xwayland->xwm, pixels, stride, width, height, - hotspot_x, hotspot_y); + xwm_set_cursor(xwayland->xwm, buffer, hotspot_x, hotspot_y); return; } - free(xwayland->cursor); - - xwayland->cursor = calloc(1, sizeof(*xwayland->cursor)); - if (xwayland->cursor == NULL) { - return; - } - xwayland->cursor->pixels = pixels; - xwayland->cursor->stride = stride; - xwayland->cursor->width = width; - xwayland->cursor->height = height; - xwayland->cursor->hotspot_x = hotspot_x; - xwayland->cursor->hotspot_y = hotspot_y; + wlr_buffer_unlock(xwayland->cursor_buffer); + xwayland->cursor_buffer = wlr_buffer_lock(xwayland->cursor_buffer); + xwayland->cursor_hotspot.x = hotspot_x; + xwayland->cursor_hotspot.y = hotspot_y; } static void xwayland_handle_seat_destroy(struct wl_listener *listener, diff --git a/xwayland/xwm.c b/xwayland/xwm.c index 2bb4e4c64..08ce04394 100644 --- a/xwayland/xwm.c +++ b/xwayland/xwm.c @@ -1,6 +1,8 @@ #include +#include #include #include +#include #include #include #include @@ -2487,8 +2489,8 @@ static void xwm_get_render_format(struct wlr_xwm *xwm) { free(reply); } -void xwm_set_cursor(struct wlr_xwm *xwm, const uint8_t *pixels, uint32_t stride, - uint32_t width, uint32_t height, int32_t hotspot_x, int32_t hotspot_y) { +void xwm_set_cursor(struct wlr_xwm *xwm, struct wlr_buffer *buffer, + int32_t hotspot_x, int32_t hotspot_y) { if (!xwm->render_format_id) { wlr_log(WLR_ERROR, "Cannot set xwm cursor: no render format available"); return; @@ -2497,11 +2499,24 @@ void xwm_set_cursor(struct wlr_xwm *xwm, const uint8_t *pixels, uint32_t stride, xcb_free_cursor(xwm->xcb_conn, xwm->cursor); } + void *pixels = NULL; + uint32_t format = DRM_FORMAT_INVALID; + size_t stride = 0; + if (!wlr_buffer_begin_data_ptr_access(buffer, WLR_BUFFER_DATA_PTR_ACCESS_READ, + &pixels, &format, &stride)) { + return; + } + + if (format != DRM_FORMAT_ARGB8888) { + wlr_buffer_end_data_ptr_access(buffer); + wlr_log(WLR_ERROR, "Only ARGB8888 is supported for Xwayland cursors"); + return; + } int depth = 32; xcb_pixmap_t pix = xcb_generate_id(xwm->xcb_conn); - xcb_create_pixmap(xwm->xcb_conn, depth, pix, xwm->screen->root, width, - height); + xcb_create_pixmap(xwm->xcb_conn, depth, pix, xwm->screen->root, buffer->width, + buffer->height); xcb_render_picture_t pic = xcb_generate_id(xwm->xcb_conn); xcb_render_create_picture(xwm->xcb_conn, pic, pix, xwm->render_format_id, @@ -2511,10 +2526,12 @@ void xwm_set_cursor(struct wlr_xwm *xwm, const uint8_t *pixels, uint32_t stride, xcb_create_gc(xwm->xcb_conn, gc, pix, 0, NULL); xcb_put_image(xwm->xcb_conn, XCB_IMAGE_FORMAT_Z_PIXMAP, pix, gc, - width, height, 0, 0, 0, depth, stride * height * sizeof(uint8_t), + buffer->width, buffer->height, 0, 0, 0, depth, stride * buffer->height, pixels); xcb_free_gc(xwm->xcb_conn, gc); + wlr_buffer_end_data_ptr_access(buffer); + xwm->cursor = xcb_generate_id(xwm->xcb_conn); xcb_render_create_cursor(xwm->xcb_conn, xwm->cursor, pic, hotspot_x, hotspot_y); From 6ae54dca23064e897b393283887986e5719a747f Mon Sep 17 00:00:00 2001 From: llyyr Date: Mon, 5 Jan 2026 20:53:34 +0530 Subject: [PATCH 212/311] xwayland: lock new buffer instead of the old one Fixes: 84d603acc06a --- xwayland/xwayland.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xwayland/xwayland.c b/xwayland/xwayland.c index 39ca822dc..300b582ea 100644 --- a/xwayland/xwayland.c +++ b/xwayland/xwayland.c @@ -179,7 +179,7 @@ void wlr_xwayland_set_cursor(struct wlr_xwayland *xwayland, } wlr_buffer_unlock(xwayland->cursor_buffer); - xwayland->cursor_buffer = wlr_buffer_lock(xwayland->cursor_buffer); + xwayland->cursor_buffer = wlr_buffer_lock(buffer); xwayland->cursor_hotspot.x = hotspot_x; xwayland->cursor_hotspot.y = hotspot_y; } From f93865ed1f3c675090bd92ed5e625b2b94e23621 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sun, 4 Jan 2026 17:33:28 +0100 Subject: [PATCH 213/311] ext_image_copy_capture_v1: replace schedule_frame with request_frame schedule_frame is only called when the client has submitted damage and a new frame should be rendered immediately. schedule_frame is not called when the capture client is waiting for the next frame but hasn't submitted damage. Sources implementing a rendering loop based on the capture rate need to know when a capture client is ready to accept a new frame. Such sources want to trigger a redraw if and only if (1) they are dirty (their contents have changed) and (2) the capture client is ready to accept a new frame. Replace schedule_frame with request_frame, triggered each time a client sends a capture request. A flag indicates whether the capture client has submitted damage. --- .../wlr_ext_image_capture_source_v1.h | 2 +- types/ext_image_capture_source_v1/output.c | 20 ++++++++++++------- types/ext_image_capture_source_v1/scene.c | 9 ++++++--- types/wlr_ext_image_copy_capture_v1.c | 6 +++--- 4 files changed, 23 insertions(+), 14 deletions(-) diff --git a/include/wlr/interfaces/wlr_ext_image_capture_source_v1.h b/include/wlr/interfaces/wlr_ext_image_capture_source_v1.h index 8678d3c67..a87e8afd6 100644 --- a/include/wlr/interfaces/wlr_ext_image_capture_source_v1.h +++ b/include/wlr/interfaces/wlr_ext_image_capture_source_v1.h @@ -21,7 +21,7 @@ struct wlr_ext_image_capture_source_v1_interface { // TODO: drop with_cursors flag void (*start)(struct wlr_ext_image_capture_source_v1 *source, bool with_cursors); void (*stop)(struct wlr_ext_image_capture_source_v1 *source); - void (*schedule_frame)(struct wlr_ext_image_capture_source_v1 *source); + void (*request_frame)(struct wlr_ext_image_capture_source_v1 *source, bool schedule_frame); void (*copy_frame)(struct wlr_ext_image_capture_source_v1 *source, struct wlr_ext_image_copy_capture_frame_v1 *dst_frame, struct wlr_ext_image_capture_source_v1_frame_event *frame_event); diff --git a/types/ext_image_capture_source_v1/output.c b/types/ext_image_capture_source_v1/output.c index 1112b64d5..a737b4e58 100644 --- a/types/ext_image_capture_source_v1/output.c +++ b/types/ext_image_capture_source_v1/output.c @@ -70,9 +70,12 @@ static void output_source_stop(struct wlr_ext_image_capture_source_v1 *base) { } } -static void output_source_schedule_frame(struct wlr_ext_image_capture_source_v1 *base) { +static void output_source_request_frame(struct wlr_ext_image_capture_source_v1 *base, + bool schedule_frame) { struct wlr_ext_output_image_capture_source_v1 *source = wl_container_of(base, source, base); - wlr_output_update_needs_frame(source->output); + if (schedule_frame) { + wlr_output_update_needs_frame(source->output); + } } static void output_source_copy_frame(struct wlr_ext_image_capture_source_v1 *base, @@ -99,7 +102,7 @@ static struct wlr_ext_image_capture_source_v1_cursor *output_source_get_pointer_ static const struct wlr_ext_image_capture_source_v1_interface output_source_impl = { .start = output_source_start, .stop = output_source_stop, - .schedule_frame = output_source_schedule_frame, + .request_frame = output_source_request_frame, .copy_frame = output_source_copy_frame, .get_pointer_cursor = output_source_get_pointer_cursor, }; @@ -258,10 +261,13 @@ struct wlr_ext_output_image_capture_source_manager_v1 *wlr_ext_output_image_capt return manager; } -static void output_cursor_source_schedule_frame(struct wlr_ext_image_capture_source_v1 *base) { +static void output_cursor_source_request_frame(struct wlr_ext_image_capture_source_v1 *base, + bool schedule_frame) { struct output_cursor_source *cursor_source = wl_container_of(base, cursor_source, base); - wlr_output_update_needs_frame(cursor_source->output); - cursor_source->needs_frame = true; + if (schedule_frame) { + wlr_output_update_needs_frame(cursor_source->output); + cursor_source->needs_frame = true; + } } static void output_cursor_source_copy_frame(struct wlr_ext_image_capture_source_v1 *base, @@ -288,7 +294,7 @@ static void output_cursor_source_copy_frame(struct wlr_ext_image_capture_source_ } static const struct wlr_ext_image_capture_source_v1_interface output_cursor_source_impl = { - .schedule_frame = output_cursor_source_schedule_frame, + .request_frame = output_cursor_source_request_frame, .copy_frame = output_cursor_source_copy_frame, }; diff --git a/types/ext_image_capture_source_v1/scene.c b/types/ext_image_capture_source_v1/scene.c index b1e46dcad..9f6104be9 100644 --- a/types/ext_image_capture_source_v1/scene.c +++ b/types/ext_image_capture_source_v1/scene.c @@ -128,9 +128,12 @@ static void source_stop(struct wlr_ext_image_capture_source_v1 *base) { wlr_output_state_finish(&state); } -static void source_schedule_frame(struct wlr_ext_image_capture_source_v1 *base) { +static void source_request_frame(struct wlr_ext_image_capture_source_v1 *base, + bool schedule_frame) { struct scene_node_source *source = wl_container_of(base, source, base); - wlr_output_update_needs_frame(&source->output); + if (schedule_frame) { + wlr_output_update_needs_frame(&source->output); + } } static void source_copy_frame(struct wlr_ext_image_capture_source_v1 *base, @@ -149,7 +152,7 @@ static void source_copy_frame(struct wlr_ext_image_capture_source_v1 *base, static const struct wlr_ext_image_capture_source_v1_interface source_impl = { .start = source_start, .stop = source_stop, - .schedule_frame = source_schedule_frame, + .request_frame = source_request_frame, .copy_frame = source_copy_frame, }; diff --git a/types/wlr_ext_image_copy_capture_v1.c b/types/wlr_ext_image_copy_capture_v1.c index c4cfd743e..bc4cdffa2 100644 --- a/types/wlr_ext_image_copy_capture_v1.c +++ b/types/wlr_ext_image_copy_capture_v1.c @@ -281,10 +281,10 @@ static void frame_handle_capture(struct wl_client *client, frame->capturing = true; - bool need_frame = !pixman_region32_empty(&frame->session->damage); + bool schedule_frame = !pixman_region32_empty(&frame->session->damage); struct wlr_ext_image_capture_source_v1 *source = frame->session->source; - if (need_frame && source->impl->schedule_frame) { - source->impl->schedule_frame(source); + if (source->impl->request_frame) { + source->impl->request_frame(source, schedule_frame); } } From 368dcfb9f6a26d6286ada74cf23b72e01fceb47e Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sun, 4 Jan 2026 17:41:17 +0100 Subject: [PATCH 214/311] ext_image_capture_source_v1: wait for capture client before sending frame event We were sending an output frame event as soon as we were done rendering. As a result, the output would render in a busy loop when a client was using the capture output for frame pacing purposes. Instead, use the request_frame hook introduced in the previous commit to throttle output frame events. Closes: https://gitlab.freedesktop.org/wlroots/wlroots/-/issues/4036 --- types/ext_image_capture_source_v1/scene.c | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/types/ext_image_capture_source_v1/scene.c b/types/ext_image_capture_source_v1/scene.c index 9f6104be9..ab0a86cac 100644 --- a/types/ext_image_capture_source_v1/scene.c +++ b/types/ext_image_capture_source_v1/scene.c @@ -19,7 +19,6 @@ struct scene_node_source { struct wlr_output output; struct wlr_scene_output *scene_output; - struct wl_event_source *idle_frame; size_t num_started; struct wl_listener node_destroy; @@ -131,6 +130,9 @@ static void source_stop(struct wlr_ext_image_capture_source_v1 *base) { static void source_request_frame(struct wlr_ext_image_capture_source_v1 *base, bool schedule_frame) { struct scene_node_source *source = wl_container_of(base, source, base); + if (source->output.frame_pending) { + wlr_output_send_frame(&source->output); + } if (schedule_frame) { wlr_output_update_needs_frame(&source->output); } @@ -170,12 +172,6 @@ static void source_update_buffer_constraints(struct scene_node_source *source, output->swapchain, output->renderer); } -static void source_handle_idle_frame(void *data) { - struct scene_node_source *source = data; - source->idle_frame = NULL; - wlr_output_send_frame(&source->output); -} - static bool output_test(struct wlr_output *output, const struct wlr_output_state *state) { struct scene_node_source *source = wl_container_of(output, source, output); @@ -211,11 +207,6 @@ static bool output_test(struct wlr_output *output, const struct wlr_output_state static bool output_commit(struct wlr_output *output, const struct wlr_output_state *state) { struct scene_node_source *source = wl_container_of(output, source, output); - if (source->idle_frame != NULL) { - wlr_log(WLR_DEBUG, "Failed to commit capture output: a frame is still pending"); - return false; - } - if ((state->committed & WLR_OUTPUT_STATE_ENABLED) && !state->enabled) { return true; } @@ -253,9 +244,6 @@ static bool output_commit(struct wlr_output *output, const struct wlr_output_sta pixman_region32_fini(&full_damage); - source->idle_frame = - wl_event_loop_add_idle(output->event_loop, source_handle_idle_frame, source); - return true; } From 3b9f599cbe7aab8bf88ab0880f52dbaf25274665 Mon Sep 17 00:00:00 2001 From: Peter Kaplan Date: Sat, 3 Jan 2026 21:25:27 +0100 Subject: [PATCH 215/311] wlr_ext_image_copy_capture_v1: new_session event And a destroy event in wlr_ext_image_copy_capture_session_v1. --- .../wlr/types/wlr_ext_image_copy_capture_v1.h | 22 +++++++++++++++ types/wlr_ext_image_copy_capture_v1.c | 27 ++++++++++--------- 2 files changed, 36 insertions(+), 13 deletions(-) diff --git a/include/wlr/types/wlr_ext_image_copy_capture_v1.h b/include/wlr/types/wlr_ext_image_copy_capture_v1.h index 9b1ab3df1..0b02b9808 100644 --- a/include/wlr/types/wlr_ext_image_copy_capture_v1.h +++ b/include/wlr/types/wlr_ext_image_copy_capture_v1.h @@ -19,11 +19,33 @@ struct wlr_renderer; struct wlr_ext_image_copy_capture_manager_v1 { struct wl_global *global; + struct { + struct wl_signal new_session; // wlr_ext_image_copy_capture_session_v1 + } events; + struct { struct wl_listener display_destroy; } WLR_PRIVATE; }; +struct wlr_ext_image_copy_capture_session_v1 { + struct wl_resource *resource; + struct wlr_ext_image_capture_source_v1 *source; + struct wlr_ext_image_copy_capture_frame_v1 *frame; + + struct { + struct wl_signal destroy; + } events; + + struct { + struct wl_listener source_destroy; + struct wl_listener source_constraints_update; + struct wl_listener source_frame; + + pixman_region32_t damage; + } WLR_PRIVATE; +}; + struct wlr_ext_image_copy_capture_frame_v1 { struct wl_resource *resource; bool capturing; diff --git a/types/wlr_ext_image_copy_capture_v1.c b/types/wlr_ext_image_copy_capture_v1.c index bc4cdffa2..72f25d5b5 100644 --- a/types/wlr_ext_image_copy_capture_v1.c +++ b/types/wlr_ext_image_copy_capture_v1.c @@ -11,18 +11,6 @@ #define IMAGE_COPY_CAPTURE_MANAGER_V1_VERSION 1 -struct wlr_ext_image_copy_capture_session_v1 { - struct wl_resource *resource; - struct wlr_ext_image_capture_source_v1 *source; - struct wlr_ext_image_copy_capture_frame_v1 *frame; - - struct wl_listener source_destroy; - struct wl_listener source_constraints_update; - struct wl_listener source_frame; - - pixman_region32_t damage; -}; - struct wlr_ext_image_copy_capture_cursor_session_v1 { struct wl_resource *resource; struct wlr_ext_image_capture_source_v1_cursor *source; @@ -385,6 +373,8 @@ static void session_destroy(struct wlr_ext_image_copy_capture_session_v1 *sessio return; } + wl_signal_emit_mutable(&session->events.destroy, NULL); + if (session->frame != NULL) { wlr_ext_image_copy_capture_frame_v1_fail(session->frame, EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_STOPPED); @@ -401,6 +391,9 @@ static void session_destroy(struct wlr_ext_image_copy_capture_session_v1 *sessio wl_list_remove(&session->source_destroy.link); wl_list_remove(&session->source_constraints_update.link); wl_list_remove(&session->source_frame.link); + + assert(wl_list_empty(&session->events.destroy.listener_list)); + free(session); } @@ -439,6 +432,7 @@ static void session_handle_resource_destroy(struct wl_resource *resource) { static void session_create(struct wl_resource *parent_resource, uint32_t new_id, struct wlr_ext_image_capture_source_v1 *source, uint32_t options) { + struct wlr_ext_image_copy_capture_manager_v1 *manager = wl_resource_get_user_data(parent_resource); struct wl_client *client = wl_resource_get_client(parent_resource); uint32_t version = wl_resource_get_version(parent_resource); struct wl_resource *session_resource = wl_resource_create(client, @@ -481,6 +475,9 @@ static void session_create(struct wl_resource *parent_resource, uint32_t new_id, wl_resource_set_user_data(session_resource, session); session_send_constraints(session); + + wl_signal_init(&session->events.destroy); + wl_signal_emit_mutable(&manager->events.new_session, session); } static void cursor_session_destroy(struct wlr_ext_image_copy_capture_cursor_session_v1 *cursor_session) { @@ -649,18 +646,20 @@ static const struct ext_image_copy_capture_manager_v1_interface manager_impl = { static void manager_bind(struct wl_client *client, void *data, uint32_t version, uint32_t id) { + struct wlr_ext_image_copy_capture_manager_v1 *manager = data; struct wl_resource *resource = wl_resource_create(client, &ext_image_copy_capture_manager_v1_interface, version, id); if (!resource) { wl_client_post_no_memory(client); return; } - wl_resource_set_implementation(resource, &manager_impl, NULL, NULL); + wl_resource_set_implementation(resource, &manager_impl, manager, NULL); } static void manager_handle_display_destroy(struct wl_listener *listener, void *data) { struct wlr_ext_image_copy_capture_manager_v1 *manager = wl_container_of(listener, manager, display_destroy); + assert(wl_list_empty(&manager->events.new_session.listener_list)); wl_list_remove(&manager->display_destroy.link); wl_global_destroy(manager->global); free(manager); @@ -685,5 +684,7 @@ struct wlr_ext_image_copy_capture_manager_v1 *wlr_ext_image_copy_capture_manager manager->display_destroy.notify = manager_handle_display_destroy; wl_display_add_destroy_listener(display, &manager->display_destroy); + wl_signal_init(&manager->events.new_session); + return manager; } From 49576469ec579cc3117ece20a02322c0a2bd81a8 Mon Sep 17 00:00:00 2001 From: Peter Kaplan Date: Mon, 5 Jan 2026 18:49:30 +0100 Subject: [PATCH 216/311] image_capture_source: wlr_output_try_from_ext_image_capture_source_v1() --- include/wlr/types/wlr_ext_image_capture_source_v1.h | 8 ++++++++ types/ext_image_capture_source_v1/output.c | 10 ++++++++++ 2 files changed, 18 insertions(+) diff --git a/include/wlr/types/wlr_ext_image_capture_source_v1.h b/include/wlr/types/wlr_ext_image_capture_source_v1.h index e38f68795..6cbbacf76 100644 --- a/include/wlr/types/wlr_ext_image_capture_source_v1.h +++ b/include/wlr/types/wlr_ext_image_capture_source_v1.h @@ -131,4 +131,12 @@ struct wlr_ext_image_capture_source_v1 *wlr_ext_image_capture_source_v1_create_w struct wlr_scene_node *node, struct wl_event_loop *event_loop, struct wlr_allocator *allocator, struct wlr_renderer *renderer); +/** + * Returns the corresponding wlr_output for a image capture source + * managed by wlr_ext_output_image_capture_source_manager_v1 + * or NULL if the image capture source is not managed by + * wlr_ext_output_image_capture_source_manager_v1. + */ +struct wlr_output *wlr_output_try_from_ext_image_capture_source_v1(struct wlr_ext_image_capture_source_v1 *source); + #endif diff --git a/types/ext_image_capture_source_v1/output.c b/types/ext_image_capture_source_v1/output.c index a737b4e58..75fa15f4d 100644 --- a/types/ext_image_capture_source_v1/output.c +++ b/types/ext_image_capture_source_v1/output.c @@ -392,3 +392,13 @@ static void output_cursor_source_finish(struct output_cursor_source *cursor_sour wl_list_remove(&cursor_source->output_commit.link); wl_list_remove(&cursor_source->prev_buffer_release.link); } + +struct wlr_output *wlr_output_try_from_ext_image_capture_source_v1(struct wlr_ext_image_capture_source_v1 *source) { + if (source->impl == &output_source_impl) { + struct wlr_ext_output_image_capture_source_v1 *output_source = + (struct wlr_ext_output_image_capture_source_v1*)source; + return output_source->output; + } + + return NULL; +} From edc12e81d9f8a66161e7e18edebff678a9d026f1 Mon Sep 17 00:00:00 2001 From: Alexander Orzechowski Date: Wed, 7 Jan 2026 12:21:24 -0500 Subject: [PATCH 217/311] wlr_scene: Make restack_xwayland_surface_below generic more stuff will be put here --- types/scene/wlr_scene.c | 49 +++++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/types/scene/wlr_scene.c b/types/scene/wlr_scene.c index 16a748e79..19f415911 100644 --- a/types/scene/wlr_scene.c +++ b/types/scene/wlr_scene.c @@ -569,25 +569,6 @@ static void restack_xwayland_surface(struct wlr_scene_node *node, data->restack_above = xwayland_surface; } - -static void restack_xwayland_surface_below(struct wlr_scene_node *node) { - if (node->type == WLR_SCENE_NODE_TREE) { - struct wlr_scene_tree *scene_tree = wlr_scene_tree_from_node(node); - struct wlr_scene_node *child; - wl_list_for_each(child, &scene_tree->children, link) { - restack_xwayland_surface_below(child); - } - return; - } - - struct wlr_xwayland_surface *xwayland_surface = - scene_node_try_get_managed_xwayland_surface(node); - if (!xwayland_surface) { - return; - } - - wlr_xwayland_surface_restack(xwayland_surface, NULL, XCB_STACK_MODE_BELOW); -} #endif static bool scene_node_update_iterator(struct wlr_scene_node *node, @@ -685,17 +666,37 @@ static void scene_update_region(struct wlr_scene *scene, pixman_region32_fini(&visible); } +static void scene_node_cleanup_when_disabled(struct wlr_scene_node *node, bool xwayland_restack) { + if (node->type == WLR_SCENE_NODE_TREE) { + struct wlr_scene_tree *scene_tree = wlr_scene_tree_from_node(node); + struct wlr_scene_node *child; + wl_list_for_each(child, &scene_tree->children, link) { + scene_node_cleanup_when_disabled(child, xwayland_restack); + } + return; + } + +#if WLR_HAS_XWAYLAND + if (xwayland_restack) { + struct wlr_xwayland_surface *xwayland_surface = + scene_node_try_get_managed_xwayland_surface(node); + if (!xwayland_surface) { + return; + } + + wlr_xwayland_surface_restack(xwayland_surface, NULL, XCB_STACK_MODE_BELOW); + } +#endif +} + static void scene_node_update(struct wlr_scene_node *node, pixman_region32_t *damage) { struct wlr_scene *scene = scene_node_get_root(node); int x, y; if (!wlr_scene_node_coords(node, &x, &y)) { -#if WLR_HAS_XWAYLAND - if (scene->restack_xwayland_surfaces) { - restack_xwayland_surface_below(node); - } -#endif + scene_node_cleanup_when_disabled(node, scene->restack_xwayland_surfaces); + if (damage) { scene_update_region(scene, damage); scene_damage_outputs(scene, damage); From 56eb27ab0b87532649ad6d1a7cc9671f9725f0bd Mon Sep 17 00:00:00 2001 From: Alexander Orzechowski Date: Wed, 7 Jan 2026 12:06:54 -0500 Subject: [PATCH 218/311] wlr_scene: Don't recurse when disabling scene node if a child is already disabled If a child is disabled, that means the node already went through the rigamarole of cleaning up its state. Don't do it again. --- types/scene/wlr_scene.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/types/scene/wlr_scene.c b/types/scene/wlr_scene.c index 19f415911..a4f32cff2 100644 --- a/types/scene/wlr_scene.c +++ b/types/scene/wlr_scene.c @@ -671,6 +671,10 @@ static void scene_node_cleanup_when_disabled(struct wlr_scene_node *node, bool x struct wlr_scene_tree *scene_tree = wlr_scene_tree_from_node(node); struct wlr_scene_node *child; wl_list_for_each(child, &scene_tree->children, link) { + if (!child->enabled) { + continue; + } + scene_node_cleanup_when_disabled(child, xwayland_restack); } return; From ef19f52ca9a69db5a9de7708afd8c410dc05a581 Mon Sep 17 00:00:00 2001 From: Alexander Orzechowski Date: Wed, 7 Jan 2026 12:11:28 -0500 Subject: [PATCH 219/311] wlr_scene: Update outputs when node is disabled --- types/scene/wlr_scene.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/types/scene/wlr_scene.c b/types/scene/wlr_scene.c index a4f32cff2..8b13c879a 100644 --- a/types/scene/wlr_scene.c +++ b/types/scene/wlr_scene.c @@ -666,7 +666,8 @@ static void scene_update_region(struct wlr_scene *scene, pixman_region32_fini(&visible); } -static void scene_node_cleanup_when_disabled(struct wlr_scene_node *node, bool xwayland_restack) { +static void scene_node_cleanup_when_disabled(struct wlr_scene_node *node, + bool xwayland_restack, struct wl_list *outputs) { if (node->type == WLR_SCENE_NODE_TREE) { struct wlr_scene_tree *scene_tree = wlr_scene_tree_from_node(node); struct wlr_scene_node *child; @@ -675,11 +676,14 @@ static void scene_node_cleanup_when_disabled(struct wlr_scene_node *node, bool x continue; } - scene_node_cleanup_when_disabled(child, xwayland_restack); + scene_node_cleanup_when_disabled(child, xwayland_restack, outputs); } return; } + pixman_region32_clear(&node->visible); + update_node_update_outputs(node, outputs, NULL, NULL); + #if WLR_HAS_XWAYLAND if (xwayland_restack) { struct wlr_xwayland_surface *xwayland_surface = @@ -699,7 +703,7 @@ static void scene_node_update(struct wlr_scene_node *node, int x, y; if (!wlr_scene_node_coords(node, &x, &y)) { - scene_node_cleanup_when_disabled(node, scene->restack_xwayland_surfaces); + scene_node_cleanup_when_disabled(node, scene->restack_xwayland_surfaces, &scene->outputs); if (damage) { scene_update_region(scene, damage); From ed7b633669de64615f91c17259255fa0b5d41d80 Mon Sep 17 00:00:00 2001 From: Alexander Orzechowski Date: Wed, 7 Jan 2026 12:12:38 -0500 Subject: [PATCH 220/311] wlr_scene: Only do disable cleanup when explicit damage is given We don't want to iterate the subtree for trivial update scenarios. We only care when a scene is reparented or disabled in which both cases provides us with explicit damage --- types/scene/wlr_scene.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/types/scene/wlr_scene.c b/types/scene/wlr_scene.c index 8b13c879a..9bde2678f 100644 --- a/types/scene/wlr_scene.c +++ b/types/scene/wlr_scene.c @@ -703,9 +703,9 @@ static void scene_node_update(struct wlr_scene_node *node, int x, y; if (!wlr_scene_node_coords(node, &x, &y)) { - scene_node_cleanup_when_disabled(node, scene->restack_xwayland_surfaces, &scene->outputs); - if (damage) { + scene_node_cleanup_when_disabled(node, scene->restack_xwayland_surfaces, &scene->outputs); + scene_update_region(scene, damage); scene_damage_outputs(scene, damage); pixman_region32_fini(damage); From 1f0fb95e3b352a89ed08330d827d3689e055a6fb Mon Sep 17 00:00:00 2001 From: Alexander Orzechowski Date: Thu, 8 Jan 2026 13:01:29 -0500 Subject: [PATCH 221/311] wlr_scene: Add documentation to scene_node_update() --- types/scene/wlr_scene.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/types/scene/wlr_scene.c b/types/scene/wlr_scene.c index 9bde2678f..e7d628d01 100644 --- a/types/scene/wlr_scene.c +++ b/types/scene/wlr_scene.c @@ -697,12 +697,28 @@ static void scene_node_cleanup_when_disabled(struct wlr_scene_node *node, #endif } +/** + * Updates the nodes visibility, xwayland restacking, send leave/enter events + * and damages the screen. The damage region is used to not only damage the + * screen, but to direct the update logic to only update certain parts of the + * screen and not the whole thing. If a NULL damage is given, the damage is + * assumed to be the previous nodes visibility. + * + * Currently, the only usage for an explicit damage is for update scenarios where + * the scene node might be enabled/disabled. If the scene node is disabled, the + * update logic will ignore the node. This is normally desirable as most update + * scenarios like updating the color or whatever. However, it's not what we want + * when disabling the node. Note that reparenting the node could lead to the node + * being reparented to a disabled super tree. + */ static void scene_node_update(struct wlr_scene_node *node, pixman_region32_t *damage) { struct wlr_scene *scene = scene_node_get_root(node); int x, y; if (!wlr_scene_node_coords(node, &x, &y)) { + // We assume explicit damage on a disabled tree means the node was just + // disabled. if (damage) { scene_node_cleanup_when_disabled(node, scene->restack_xwayland_surfaces, &scene->outputs); From ba76d37b0bed26ad32aec1b6bce4675ad2926246 Mon Sep 17 00:00:00 2001 From: Robin Ebert Date: Mon, 12 Jan 2026 12:40:34 +0100 Subject: [PATCH 222/311] wlr_ext_image_copy_capture_v1: Fix segmentation fault when using cursor session Fixes: 3b9f599cbe7aab8bf88ab0880f52dbaf25274665 --- types/wlr_ext_image_copy_capture_v1.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/types/wlr_ext_image_copy_capture_v1.c b/types/wlr_ext_image_copy_capture_v1.c index 72f25d5b5..c9e7b0079 100644 --- a/types/wlr_ext_image_copy_capture_v1.c +++ b/types/wlr_ext_image_copy_capture_v1.c @@ -14,6 +14,7 @@ struct wlr_ext_image_copy_capture_cursor_session_v1 { struct wl_resource *resource; struct wlr_ext_image_capture_source_v1_cursor *source; + struct wlr_ext_image_copy_capture_manager_v1 *manager; bool capture_session_created; struct { @@ -28,10 +29,18 @@ struct wlr_ext_image_copy_capture_cursor_session_v1 { struct wl_listener source_update; }; +static const struct ext_image_copy_capture_manager_v1_interface manager_impl; static const struct ext_image_copy_capture_frame_v1_interface frame_impl; static const struct ext_image_copy_capture_session_v1_interface session_impl; static const struct ext_image_copy_capture_cursor_session_v1_interface cursor_session_impl; +static struct wlr_ext_image_copy_capture_manager_v1 *manager_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &ext_image_copy_capture_manager_v1_interface, &manager_impl)); + return wl_resource_get_user_data(resource); +} + static struct wlr_ext_image_copy_capture_frame_v1 *frame_from_resource( struct wl_resource *resource) { assert(wl_resource_instance_of(resource, @@ -431,8 +440,7 @@ static void session_handle_resource_destroy(struct wl_resource *resource) { } static void session_create(struct wl_resource *parent_resource, uint32_t new_id, - struct wlr_ext_image_capture_source_v1 *source, uint32_t options) { - struct wlr_ext_image_copy_capture_manager_v1 *manager = wl_resource_get_user_data(parent_resource); + struct wlr_ext_image_capture_source_v1 *source, uint32_t options, struct wlr_ext_image_copy_capture_manager_v1 *manager) { struct wl_client *client = wl_resource_get_client(parent_resource); uint32_t version = wl_resource_get_version(parent_resource); struct wl_resource *session_resource = wl_resource_create(client, @@ -517,7 +525,7 @@ static void cursor_session_handle_get_capture_session(struct wl_client *client, source = &cursor_session->source->base; } - session_create(cursor_session_resource, new_id, source, 0); + session_create(cursor_session_resource, new_id, source, 0, cursor_session->manager); } static const struct ext_image_copy_capture_cursor_session_v1_interface cursor_session_impl = { @@ -580,7 +588,7 @@ static void manager_handle_create_session(struct wl_client *client, struct wl_resource *source_resource, uint32_t options) { struct wlr_ext_image_capture_source_v1 *source = wlr_ext_image_capture_source_v1_from_resource(source_resource); - session_create(manager_resource, new_id, source, options); + session_create(manager_resource, new_id, source, options, manager_from_resource(manager_resource)); } static void manager_handle_create_pointer_cursor_session(struct wl_client *client, @@ -621,6 +629,7 @@ static void manager_handle_create_pointer_cursor_session(struct wl_client *clien cursor_session->resource = cursor_session_resource; cursor_session->source = source_cursor; + cursor_session->manager = manager_from_resource(manager_resource); cursor_session->source_destroy.notify = cursor_session_handle_source_destroy; wl_signal_add(&source_cursor->base.events.destroy, &cursor_session->source_destroy); From f735439543a483348def37272e1bcf59696e639b Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Thu, 15 Jan 2026 11:49:49 +0100 Subject: [PATCH 223/311] xwayland: add set_size_hints signal Currently there is no way for the compositor to be notified when an Xwayland client sets/modifies its size hints. --- include/wlr/xwayland/xwayland.h | 1 + xwayland/xwm.c | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/include/wlr/xwayland/xwayland.h b/include/wlr/xwayland/xwayland.h index c3704aad1..f36912b71 100644 --- a/include/wlr/xwayland/xwayland.h +++ b/include/wlr/xwayland/xwayland.h @@ -221,6 +221,7 @@ struct wlr_xwayland_surface { struct wl_signal set_startup_id; struct wl_signal set_window_type; struct wl_signal set_hints; + struct wl_signal set_size_hints; struct wl_signal set_decorations; struct wl_signal set_strut_partial; struct wl_signal set_override_redirect; diff --git a/xwayland/xwm.c b/xwayland/xwm.c index 08ce04394..a6b3208ce 100644 --- a/xwayland/xwm.c +++ b/xwayland/xwm.c @@ -239,6 +239,7 @@ static struct wlr_xwayland_surface *xwayland_surface_create( wl_signal_init(&surface->events.set_startup_id); wl_signal_init(&surface->events.set_window_type); wl_signal_init(&surface->events.set_hints); + wl_signal_init(&surface->events.set_size_hints); wl_signal_init(&surface->events.set_decorations); wl_signal_init(&surface->events.set_strut_partial); wl_signal_init(&surface->events.set_override_redirect); @@ -601,6 +602,7 @@ static void xwayland_surface_destroy(struct wlr_xwayland_surface *xsurface) { assert(wl_list_empty(&xsurface->events.set_startup_id.listener_list)); assert(wl_list_empty(&xsurface->events.set_window_type.listener_list)); assert(wl_list_empty(&xsurface->events.set_hints.listener_list)); + assert(wl_list_empty(&xsurface->events.set_size_hints.listener_list)); assert(wl_list_empty(&xsurface->events.set_decorations.listener_list)); assert(wl_list_empty(&xsurface->events.set_strut_partial.listener_list)); assert(wl_list_empty(&xsurface->events.set_override_redirect.listener_list)); @@ -951,6 +953,8 @@ static void read_surface_normal_hints(struct wlr_xwm *xwm, xsurface->size_hints->max_width = -1; xsurface->size_hints->max_height = -1; } + + wl_signal_emit_mutable(&xsurface->events.set_size_hints, NULL); } #define MWM_HINTS_FLAGS_FIELD 0 From b7312b4f9585781eb5ab0e884b7616ad5f73ba4c Mon Sep 17 00:00:00 2001 From: Stephane Fontaine Date: Sun, 18 Jan 2026 17:42:25 +0400 Subject: [PATCH 224/311] xwm: don't leak msg in case of realloc failure --- xwayland/xwm.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/xwayland/xwm.c b/xwayland/xwm.c index a6b3208ce..c8eac2ce1 100644 --- a/xwayland/xwm.c +++ b/xwayland/xwm.c @@ -1775,11 +1775,12 @@ static void xwm_handle_net_startup_info_message(struct wlr_xwm *xwm, char *start; size_t buf_len = sizeof(ev->data); if (curr) { - curr->msg = realloc(curr->msg, curr->len + buf_len); - if (!curr->msg) { + char *msg = realloc(curr->msg, curr->len + buf_len); + if (!msg) { pending_startup_id_destroy(curr); return; } + curr->msg = msg; start = curr->msg + curr->len; curr->len += buf_len; } else { From 3eb07b1fdb869c3fd264a1ddbf0b6f341db57883 Mon Sep 17 00:00:00 2001 From: Jens Peters Date: Mon, 19 Jan 2026 20:42:42 +0100 Subject: [PATCH 225/311] backend/libinput: expose libinput_tablet_tool Exposing libinput_tablet_tool from libinput allows compositors to set tool specific configuration options like pressure range and eraser button behavior. See 'libinput_tablet_tool_*()' functions which require a 'libinput_tablet_tool' handle: https://wayland.freedesktop.org/libinput/doc/1.30.0/api/group__config.html (libinput 1.30.0, latest at the time of writing). --- backend/libinput/tablet_tool.c | 10 ++++++++++ include/wlr/backend/libinput.h | 6 ++++++ 2 files changed, 16 insertions(+) diff --git a/backend/libinput/tablet_tool.c b/backend/libinput/tablet_tool.c index d43c6cd0a..b2430a445 100644 --- a/backend/libinput/tablet_tool.c +++ b/backend/libinput/tablet_tool.c @@ -18,6 +18,16 @@ const struct wlr_tablet_impl libinput_tablet_impl = { .name = "libinput-tablet-tool", }; +struct libinput_tablet_tool *wlr_libinput_get_tablet_tool_handle( + struct wlr_tablet_tool *wlr_tablet_tool) { + + struct tablet_tool *tool = + wl_container_of(wlr_tablet_tool, tool, wlr_tool); + + assert(tool); + return tool->handle; +} + void init_device_tablet(struct wlr_libinput_input_device *dev) { const char *name = get_libinput_device_name(dev->handle); struct wlr_tablet *wlr_tablet = &dev->tablet; diff --git a/include/wlr/backend/libinput.h b/include/wlr/backend/libinput.h index 663f71acd..2a4e1996e 100644 --- a/include/wlr/backend/libinput.h +++ b/include/wlr/backend/libinput.h @@ -15,6 +15,7 @@ #include struct wlr_input_device; +struct wlr_tablet_tool; struct wlr_backend *wlr_libinput_backend_create(struct wlr_session *session); /** @@ -22,6 +23,11 @@ struct wlr_backend *wlr_libinput_backend_create(struct wlr_session *session); */ struct libinput_device *wlr_libinput_get_device_handle( struct wlr_input_device *dev); +/** + * Gets the underlying struct libinput_tablet_tool handle for the given tablet tool. + */ +struct libinput_tablet_tool *wlr_libinput_get_tablet_tool_handle( + struct wlr_tablet_tool *wlr_tablet_tool); bool wlr_backend_is_libinput(struct wlr_backend *backend); bool wlr_input_device_is_libinput(struct wlr_input_device *device); From 91c08d5a53a191669487eb2a8dff9096db1cbac6 Mon Sep 17 00:00:00 2001 From: xurui Date: Tue, 20 Jan 2026 16:29:24 +0800 Subject: [PATCH 226/311] xwayland/selection/dnd: fix parameter type Signed-off-by: xurui --- xwayland/selection/dnd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xwayland/selection/dnd.c b/xwayland/selection/dnd.c index 70792b5f4..31e588baf 100644 --- a/xwayland/selection/dnd.c +++ b/xwayland/selection/dnd.c @@ -23,7 +23,7 @@ static xcb_atom_t data_device_manager_dnd_action_to_atom( static enum wl_data_device_manager_dnd_action data_device_manager_dnd_action_from_atom(struct wlr_xwm *xwm, - enum atom_name atom) { + xcb_atom_t atom) { if (atom == xwm->atoms[DND_ACTION_COPY] || atom == xwm->atoms[DND_ACTION_PRIVATE]) { return WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY; From 5661ac1cd216485ff0fa6df037cb9fa523ecd706 Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Thu, 22 Jan 2026 11:21:29 +0100 Subject: [PATCH 227/311] output-swapchain-manager: Reject zero resolution If an output with no mode and no valid pending resolution is attempted enabled, it will trigger an assert in swapchain allocation instead of failing on a rejected atomic commit or pre-commit test. We can sometimes get such broken outputs if e.g., the underlying driver failed to retrieve EDID, and so crashing on assert is suboptimal. Reject zero-sized swapchains early and log the issue. --- types/wlr_output_swapchain_manager.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/types/wlr_output_swapchain_manager.c b/types/wlr_output_swapchain_manager.c index 9df3a6524..d91d5f510 100644 --- a/types/wlr_output_swapchain_manager.c +++ b/types/wlr_output_swapchain_manager.c @@ -108,6 +108,10 @@ static bool manager_output_prepare(struct wlr_output_swapchain_manager_output *m int width, height; output_pending_resolution(output, state, &width, &height); + if (width == 0 || height == 0) { + wlr_log(WLR_DEBUG, "Cannot allocate swapchain for zero resolution (%dx%d)", width, height); + return false; + } uint32_t fmt = output->render_format; if (state->committed & WLR_OUTPUT_STATE_RENDER_FORMAT) { From e6ddb6cdd3d051e5a68b98cf972feb4347277eaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Poisot?= Date: Sat, 27 Dec 2025 16:42:50 +0000 Subject: [PATCH 228/311] meson: bump minimum wayland-protocols version 1.47 brings updates to color-management --- protocol/meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocol/meson.build b/protocol/meson.build index 613d18018..0283dad64 100644 --- a/protocol/meson.build +++ b/protocol/meson.build @@ -1,5 +1,5 @@ wayland_protos = dependency('wayland-protocols', - version: '>=1.44', + version: '>=1.47', fallback: 'wayland-protocols', default_options: ['tests=false'], ) From 58f0867c0426cd9b99af2b703b8f34824840506e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Poisot?= Date: Sat, 27 Dec 2025 14:49:38 +0000 Subject: [PATCH 229/311] color_management_v1: relax restrictions on maxCLL and maxFALL ref https://gitlab.freedesktop.org/wayland/wayland-protocols/-/merge_requests/428 --- types/wlr_color_management_v1.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/types/wlr_color_management_v1.c b/types/wlr_color_management_v1.c index 18ce6ec26..bc59f5309 100644 --- a/types/wlr_color_management_v1.c +++ b/types/wlr_color_management_v1.c @@ -469,8 +469,10 @@ static void image_desc_creator_params_handle_create(struct wl_client *client, return; } - if (!check_mastering_luminance_range(params_resource, ¶ms->data, params->data.max_cll, "max_cll") || - !check_mastering_luminance_range(params_resource, ¶ms->data, params->data.max_fall, "max_fall")) { + uint32_t version = wl_resource_get_version(params_resource); + if (version < 2 && + (!check_mastering_luminance_range(params_resource, ¶ms->data, params->data.max_cll, "max_cll") || + !check_mastering_luminance_range(params_resource, ¶ms->data, params->data.max_fall, "max_fall"))) { return; } From bb882e97f1893583b89c622eee640f34cfeed85c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Poisot?= Date: Sat, 27 Dec 2025 15:45:25 +0000 Subject: [PATCH 230/311] color_management_v1: use 64bit image description identities ref https://gitlab.freedesktop.org/wayland/wayland-protocols/-/merge_requests/385 --- include/wlr/types/wlr_color_management_v1.h | 2 +- types/wlr_color_management_v1.c | 26 +++++++++++++++++---- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/include/wlr/types/wlr_color_management_v1.h b/include/wlr/types/wlr_color_management_v1.h index 8caa61c6a..2fc46d5ea 100644 --- a/include/wlr/types/wlr_color_management_v1.h +++ b/include/wlr/types/wlr_color_management_v1.h @@ -78,7 +78,7 @@ struct wlr_color_manager_v1 { struct wl_list outputs; // wlr_color_management_output_v1.link struct wl_list surface_feedbacks; // wlr_color_management_surface_feedback_v1.link - uint32_t last_image_desc_identity; + uint64_t last_image_desc_identity; struct wl_listener display_destroy; } WLR_PRIVATE; diff --git a/types/wlr_color_management_v1.c b/types/wlr_color_management_v1.c index bc59f5309..d3aae0464 100644 --- a/types/wlr_color_management_v1.c +++ b/types/wlr_color_management_v1.c @@ -179,8 +179,16 @@ static void image_desc_create_ready(struct wlr_color_manager_v1 *manager, image_desc, image_desc_handle_resource_destroy); // TODO: de-duplicate identity - uint32_t identity = ++manager->last_image_desc_identity; - wp_image_description_v1_send_ready(image_desc->resource, identity); + uint64_t identity = ++manager->last_image_desc_identity; + uint32_t identity_hi = identity >> 32; + uint32_t identity_lo = (uint32_t)identity; + + uint32_t version = wl_resource_get_version(image_desc->resource); + if (version >= WP_IMAGE_DESCRIPTION_V1_READY2_SINCE_VERSION) { + wp_image_description_v1_send_ready2(image_desc->resource, identity_hi, identity_lo); + } else { + wp_image_description_v1_send_ready(image_desc->resource, identity_lo); + } } static void image_desc_create_failed(struct wl_resource *parent_resource, uint32_t id, @@ -975,14 +983,22 @@ void wlr_color_manager_v1_set_surface_preferred_image_description( struct wlr_color_manager_v1 *manager, struct wlr_surface *surface, const struct wlr_image_description_v1_data *data) { // TODO: de-duplicate identity - uint32_t identity = ++manager->last_image_desc_identity; + uint64_t identity = ++manager->last_image_desc_identity; + uint32_t identity_hi = identity >> 32; + uint32_t identity_lo = (uint32_t)identity; struct wlr_color_management_surface_feedback_v1 *surface_feedback; wl_list_for_each(surface_feedback, &manager->surface_feedbacks, link) { if (surface_feedback->surface == surface) { surface_feedback->data = *data; - wp_color_management_surface_feedback_v1_send_preferred_changed( - surface_feedback->resource, identity); + uint32_t version = wl_resource_get_version(surface_feedback->resource); + if (version >= WP_COLOR_MANAGEMENT_SURFACE_FEEDBACK_V1_PREFERRED_CHANGED2_SINCE_VERSION) { + wp_color_management_surface_feedback_v1_send_preferred_changed2( + surface_feedback->resource, identity_hi, identity_lo); + } else { + wp_color_management_surface_feedback_v1_send_preferred_changed( + surface_feedback->resource, identity_lo); + } } } } From 68052f34d318a6f9b2cb77074abb1105043c240a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Poisot?= Date: Sat, 27 Dec 2025 16:33:03 +0000 Subject: [PATCH 231/311] color_management_v1: new enum value for 'srgb' transfer function ref https://gitlab.freedesktop.org/wayland/wayland-protocols/-/merge_requests/442 --- types/wlr_color_management_v1.c | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/types/wlr_color_management_v1.c b/types/wlr_color_management_v1.c index d3aae0464..0a19e8e78 100644 --- a/types/wlr_color_management_v1.c +++ b/types/wlr_color_management_v1.c @@ -12,7 +12,7 @@ #include "render/color.h" #include "util/mem.h" -#define COLOR_MANAGEMENT_V1_VERSION 1 +#define COLOR_MANAGEMENT_V1_VERSION 2 struct wlr_color_management_output_v1 { struct wl_resource *resource; @@ -178,12 +178,18 @@ static void image_desc_create_ready(struct wlr_color_manager_v1 *manager, wl_resource_set_implementation(image_desc->resource, &image_desc_impl, image_desc, image_desc_handle_resource_destroy); + uint32_t version = wl_resource_get_version(image_desc->resource); + if (!wp_color_manager_v1_transfer_function_is_valid(data->tf_named, version)) { + wp_image_description_v1_send_failed(image_desc->resource, + WP_IMAGE_DESCRIPTION_V1_CAUSE_LOW_VERSION, "unhandled value for tf_named"); + return; + } + // TODO: de-duplicate identity uint64_t identity = ++manager->last_image_desc_identity; uint32_t identity_hi = identity >> 32; uint32_t identity_lo = (uint32_t)identity; - uint32_t version = wl_resource_get_version(image_desc->resource); if (version >= WP_IMAGE_DESCRIPTION_V1_READY2_SINCE_VERSION) { wp_image_description_v1_send_ready2(image_desc->resource, identity_hi, identity_lo); } else { @@ -885,8 +891,11 @@ static void manager_bind(struct wl_client *client, void *data, manager->render_intents[i]); } for (size_t i = 0; i < manager->transfer_functions_len; i++) { - wp_color_manager_v1_send_supported_tf_named(resource, - manager->transfer_functions[i]); + enum wp_color_manager_v1_transfer_function tf = manager->transfer_functions[i]; + if (!wp_color_manager_v1_transfer_function_is_valid(tf, version)) { + continue; + } + wp_color_manager_v1_send_supported_tf_named(resource, tf); } for (size_t i = 0; i < manager->primaries_len; i++) { wp_color_manager_v1_send_supported_primaries_named(resource, @@ -920,6 +929,10 @@ struct wlr_color_manager_v1 *wlr_color_manager_v1_create(struct wl_display *disp } assert(has_perceptual_render_intent); + for (size_t i = 0; i < options->transfer_functions_len; i++) { + assert(options->transfer_functions[i] != WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_SRGB); + } + // TODO: add support for all of these features assert(!options->features.icc_v2_v4); assert(!options->features.set_primaries); @@ -1006,7 +1019,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) { switch (tf) { - case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_SRGB: + case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_COMPOUND_POWER_2_4: return WLR_COLOR_TRANSFER_FUNCTION_SRGB; case WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST2084_PQ: return WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ; @@ -1025,7 +1038,7 @@ enum wp_color_manager_v1_transfer_function wlr_color_manager_v1_transfer_function_from_wlr(enum wlr_color_transfer_function tf) { switch (tf) { case WLR_COLOR_TRANSFER_FUNCTION_SRGB: - return WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_SRGB; + return WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_COMPOUND_POWER_2_4; case WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ: return WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST2084_PQ; case WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR: @@ -1069,11 +1082,11 @@ wlr_color_manager_v1_transfer_function_list_from_renderer(struct wlr_renderer *r } const enum wp_color_manager_v1_transfer_function list[] = { - WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_SRGB, WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA22, WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST2084_PQ, WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR, WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_BT1886, + WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_COMPOUND_POWER_2_4, }; enum wp_color_manager_v1_transfer_function *out = NULL; From 5a40da7e15b145cd10104cb35982649e4050281c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Poisot?= Date: Sun, 11 Jan 2026 13:59:43 +0000 Subject: [PATCH 232/311] render: don't infer luminance multipliers from color TF Make scene pass them explicitly instead. Ref #3995 --- include/render/vulkan.h | 1 - include/wlr/render/pass.h | 2 ++ render/vulkan/pass.c | 21 ++------------------- render/vulkan/shaders/output.frag | 3 --- types/output/cursor.c | 9 +++++++++ types/scene/wlr_scene.c | 23 +++++++++++++++++++++++ 6 files changed, 36 insertions(+), 23 deletions(-) diff --git a/include/render/vulkan.h b/include/render/vulkan.h index ac0af65fb..bb56b5534 100644 --- a/include/render/vulkan.h +++ b/include/render/vulkan.h @@ -372,7 +372,6 @@ struct wlr_vk_frag_texture_pcr_data { struct wlr_vk_frag_output_pcr_data { float matrix[4][4]; // only a 3x3 subset is used - float luminance_multiplier; float lut_3d_offset; float lut_3d_scale; }; diff --git a/include/wlr/render/pass.h b/include/wlr/render/pass.h index 1ef888f46..61ab77132 100644 --- a/include/wlr/render/pass.h +++ b/include/wlr/render/pass.h @@ -109,6 +109,8 @@ struct wlr_render_texture_options { enum wlr_color_encoding color_encoding; /* Color range of the source texture */ enum wlr_color_range color_range; + /* Default: 1.0 */ + const float *luminance_multiplier; /* Wait for a timeline synchronization point before texturing. * diff --git a/render/vulkan/pass.c b/render/vulkan/pass.c index db7a659d6..31f5116bd 100644 --- a/render/vulkan/pass.c +++ b/render/vulkan/pass.c @@ -146,11 +146,6 @@ static VkSemaphore render_pass_wait_sync_file(struct wlr_vk_render_pass *pass, return *sem_ptr; } -static float get_luminance_multiplier(const struct wlr_color_luminances *src_lum, - const struct wlr_color_luminances *dst_lum) { - return (dst_lum->reference / src_lum->reference) * (src_lum->max / dst_lum->max); -} - static bool unwrap_color_transform(struct wlr_color_transform *transform, float matrix[static 9], enum wlr_color_transfer_function *tf) { if (transform == NULL) { @@ -248,7 +243,6 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { } struct wlr_vk_frag_output_pcr_data frag_pcr_data = { - .luminance_multiplier = 1, .lut_3d_offset = 0.5f / dim, .lut_3d_scale = (float)(dim - 1) / dim, }; @@ -276,12 +270,6 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { pipeline = render_buffer->two_pass.render_setup->output_pipe_bt1886; break; } - - struct wlr_color_luminances srgb_lum, dst_lum; - wlr_color_transfer_function_get_default_luminance( - WLR_COLOR_TRANSFER_FUNCTION_SRGB, &srgb_lum); - wlr_color_transfer_function_get_default_luminance(tf, &dst_lum); - frag_pcr_data.luminance_multiplier = get_luminance_multiplier(&srgb_lum, &dst_lum); } bind_pipeline(pass, pipeline); vkCmdPushConstants(render_cb->vk, renderer->output_pipe_layout, @@ -883,13 +871,8 @@ static void render_pass_add_texture(struct wlr_render_pass *wlr_pass, } float luminance_multiplier = 1; - if (tf != WLR_COLOR_TRANSFER_FUNCTION_SRGB - && tf != WLR_COLOR_TRANSFER_FUNCTION_GAMMA22) { - struct wlr_color_luminances src_lum, srgb_lum; - wlr_color_transfer_function_get_default_luminance(tf, &src_lum); - wlr_color_transfer_function_get_default_luminance( - WLR_COLOR_TRANSFER_FUNCTION_SRGB, &srgb_lum); - luminance_multiplier = get_luminance_multiplier(&src_lum, &srgb_lum); + if (options->luminance_multiplier != NULL) { + luminance_multiplier = *options->luminance_multiplier; } struct wlr_vk_frag_texture_pcr_data frag_pcr_data = { diff --git a/render/vulkan/shaders/output.frag b/render/vulkan/shaders/output.frag index 0a8d9b855..e63de802f 100644 --- a/render/vulkan/shaders/output.frag +++ b/render/vulkan/shaders/output.frag @@ -10,7 +10,6 @@ layout(location = 0) out vec4 out_color; /* struct wlr_vk_frag_output_pcr_data */ layout(push_constant, row_major) uniform UBO { layout(offset = 80) mat4 matrix; - float luminance_multiplier; float lut_3d_offset; float lut_3d_scale; } data; @@ -71,8 +70,6 @@ void main() { rgb = in_color.rgb / alpha; } - rgb *= data.luminance_multiplier; - rgb = mat3(data.matrix) * rgb; if (OUTPUT_TRANSFORM != OUTPUT_TRANSFORM_IDENTITY) { diff --git a/types/output/cursor.c b/types/output/cursor.c index 8a7f9e92a..11f442cdf 100644 --- a/types/output/cursor.c +++ b/types/output/cursor.c @@ -512,6 +512,15 @@ bool output_cursor_refresh_color_transform(struct wlr_output_cursor *output_curs wlr_color_primaries_from_named(&primaries, img_desc->primaries); float matrix[9]; wlr_color_primaries_transform_absolute_colorimetric(&primaries_srgb, &primaries, matrix); + + // Source is sRGB, which has reference == max + struct wlr_color_luminances dst_lum; + wlr_color_transfer_function_get_default_luminance(img_desc->transfer_function, &dst_lum); + float luminance_multiplier = dst_lum.reference / dst_lum.max; + for (int i = 0; i < 9; ++i) { + matrix[i] *= luminance_multiplier; + } + struct wlr_color_transform *transforms[] = { wlr_color_transform_init_matrix(matrix), wlr_color_transform_init_linear_to_inverse_eotf(img_desc->transfer_function), diff --git a/types/scene/wlr_scene.c b/types/scene/wlr_scene.c index e7d628d01..19617b11f 100644 --- a/types/scene/wlr_scene.c +++ b/types/scene/wlr_scene.c @@ -1435,6 +1435,11 @@ struct render_list_entry { int x, y; }; +static float get_luminance_multiplier(const struct wlr_color_luminances *src_lum, + const struct wlr_color_luminances *dst_lum) { + return (dst_lum->reference / src_lum->reference) * (src_lum->max / dst_lum->max); +} + static void scene_entry_render(struct render_list_entry *entry, const struct render_data *data) { struct wlr_scene_node *node = entry->node; @@ -1518,6 +1523,13 @@ static void scene_entry_render(struct render_list_entry *entry, const struct ren wlr_color_primaries_from_named(&primaries, scene_buffer->primaries); } + struct wlr_color_luminances src_lum, srgb_lum; + wlr_color_transfer_function_get_default_luminance( + scene_buffer->transfer_function, &src_lum); + wlr_color_transfer_function_get_default_luminance( + WLR_COLOR_TRANSFER_FUNCTION_SRGB, &srgb_lum); + float luminance_multiplier = get_luminance_multiplier(&src_lum, &srgb_lum); + wlr_render_pass_add_texture(data->render_pass, &(struct wlr_render_texture_options) { .texture = texture, .src_box = scene_buffer->src_box, @@ -1533,6 +1545,7 @@ static void scene_entry_render(struct render_list_entry *entry, const struct ren .primaries = scene_buffer->primaries != 0 ? &primaries : NULL, .color_encoding = scene_buffer->color_encoding, .color_range = scene_buffer->color_range, + .luminance_multiplier = &luminance_multiplier, .wait_timeline = scene_buffer->wait_timeline, .wait_point = scene_buffer->wait_point, }); @@ -2208,6 +2221,16 @@ static bool scene_output_combine_color_transforms( wlr_color_primaries_from_named(&primaries, img_desc->primaries); float matrix[9]; wlr_color_primaries_transform_absolute_colorimetric(&primaries_srgb, &primaries, matrix); + + struct wlr_color_luminances srgb_lum, dst_lum; + wlr_color_transfer_function_get_default_luminance( + WLR_COLOR_TRANSFER_FUNCTION_SRGB, &srgb_lum); + wlr_color_transfer_function_get_default_luminance(img_desc->transfer_function, &dst_lum); + float luminance_multiplier = get_luminance_multiplier(&srgb_lum, &dst_lum); + for (int i = 0; i < 9; ++i) { + matrix[i] *= luminance_multiplier; + } + color_matrix = wlr_color_transform_init_matrix(matrix); inv_eotf = wlr_color_transform_init_linear_to_inverse_eotf(img_desc->transfer_function); if (color_matrix == NULL || inv_eotf == NULL) { From 5e32b6663babc1fb30a3cf796dc9efdf1fe7738e Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Thu, 15 Jan 2026 15:22:27 +0100 Subject: [PATCH 233/311] ext_image_copy_capture_v1: Only render scene source on damage wlr_scene_output_needs_frame checks wlr_output.needs_frame and wlr_scene_output.gamma_lut_changed, neither of which incur damage. The needs_frame flag is often set by e.g., cursor movement. For the purpose of a capture frame we are only interested in frames with damage. Continue without damage causes session_handle_source_frame to silently skip copying the frame, which causes the session to get stuck: no ready or failed event is emitted, and frame_pending is still set so no further output frame events will occur. Only render in case there is damage, but send frame callbacks regardless. --- types/ext_image_capture_source_v1/scene.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/types/ext_image_capture_source_v1/scene.c b/types/ext_image_capture_source_v1/scene.c index ab0a86cac..d3bf86b6c 100644 --- a/types/ext_image_capture_source_v1/scene.c +++ b/types/ext_image_capture_source_v1/scene.c @@ -95,10 +95,6 @@ static void source_render(struct scene_node_source *source) { // TODO: send failure return; } - - struct timespec now; - clock_gettime(CLOCK_MONOTONIC, &now); - wlr_scene_output_send_frame_done(scene_output, &now); } static void source_start(struct wlr_ext_image_capture_source_v1 *base, bool with_cursors) { @@ -110,6 +106,10 @@ static void source_start(struct wlr_ext_image_capture_source_v1 *base, bool with } source_render(source); + + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + wlr_scene_output_send_frame_done(source->scene_output, &now); } static void source_stop(struct wlr_ext_image_capture_source_v1 *base) { @@ -285,7 +285,14 @@ static void source_handle_output_frame(struct wl_listener *listener, void *data) return; } - source_render(source); + // We can only emit frames with damage + if (!pixman_region32_empty(&source->scene_output->pending_commit_damage)) { + source_render(source); + } + + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + wlr_scene_output_send_frame_done(source->scene_output, &now); } struct wlr_ext_image_capture_source_v1 *wlr_ext_image_capture_source_v1_create_with_scene_node( From 82d5ffb09ed659dd4530dc2361f289e6fa6e0d11 Mon Sep 17 00:00:00 2001 From: tokyo4j Date: Sun, 13 Jul 2025 23:25:15 +0900 Subject: [PATCH 234/311] ext-workspace-v1: add implementation Co-authored-by: Consolatis <40171-Consolatis@users.noreply.gitlab.freedesktop.org> --- include/wlr/types/wlr_ext_workspace_v1.h | 144 ++++ protocol/meson.build | 1 + types/meson.build | 1 + types/wlr_ext_workspace_v1.c | 944 +++++++++++++++++++++++ 4 files changed, 1090 insertions(+) create mode 100644 include/wlr/types/wlr_ext_workspace_v1.h create mode 100644 types/wlr_ext_workspace_v1.c diff --git a/include/wlr/types/wlr_ext_workspace_v1.h b/include/wlr/types/wlr_ext_workspace_v1.h new file mode 100644 index 000000000..c1b546fd1 --- /dev/null +++ b/include/wlr/types/wlr_ext_workspace_v1.h @@ -0,0 +1,144 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_EXT_WORKSPACE_V1_H +#define WLR_TYPES_WLR_EXT_WORKSPACE_V1_H + +#include +#include + +struct wlr_output; + +enum wlr_ext_workspace_v1_request_type { + WLR_EXT_WORKSPACE_V1_REQUEST_CREATE_WORKSPACE, + WLR_EXT_WORKSPACE_V1_REQUEST_ACTIVATE, + WLR_EXT_WORKSPACE_V1_REQUEST_DEACTIVATE, + WLR_EXT_WORKSPACE_V1_REQUEST_ASSIGN, + WLR_EXT_WORKSPACE_V1_REQUEST_REMOVE, +}; + +struct wlr_ext_workspace_v1_request { + enum wlr_ext_workspace_v1_request_type type; + struct wl_list link; // wlr_ext_workspace_manager_v1_resource.requests + union { + struct { + char *name; + struct wlr_ext_workspace_group_handle_v1 *group; // NULL if destroyed + } create_workspace; + struct { + struct wlr_ext_workspace_handle_v1 *workspace; // NULL if destroyed + } activate; + struct { + struct wlr_ext_workspace_handle_v1 *workspace; // NULL if destroyed + } deactivate; + struct { + struct wlr_ext_workspace_handle_v1 *workspace; // NULL if destroyed + struct wlr_ext_workspace_group_handle_v1 *group; // NULL if destroyed + } assign; + struct { + struct wlr_ext_workspace_handle_v1 *workspace; // NULL if destroyed + } remove; + }; +}; + +struct wlr_ext_workspace_v1_commit_event { + struct wl_list *requests; // wlr_ext_workspace_v1_request.link +}; + +struct wlr_ext_workspace_manager_v1 { + struct wl_global *global; + struct wl_list groups; // wlr_ext_workspace_group_handle_v1.link + struct wl_list workspaces; // wlr_ext_workspace_handle_v1.link + + struct { + struct wl_signal commit; // wlr_ext_workspace_v1_commit_event + struct wl_signal destroy; + } events; + + void *data; + + struct { + struct wl_list resources; // wlr_ext_workspace_manager_v1_resource.link + struct wl_event_source *idle_source; + struct wl_event_loop *event_loop; + struct wl_listener display_destroy; + } WLR_PRIVATE; +}; + +struct wlr_ext_workspace_group_handle_v1 { + struct wlr_ext_workspace_manager_v1 *manager; + uint32_t caps; // ext_workspace_group_handle_v1_group_capabilities + struct { + struct wl_signal destroy; + } events; + + struct wl_list link; // wlr_ext_workspace_manager_v1.groups + + void *data; + + struct { + struct wl_list outputs; // wlr_ext_workspace_v1_group_output.link + struct wl_list resources; // wlr_ext_workspace_manager_v1_resource.link + } WLR_PRIVATE; +}; + +struct wlr_ext_workspace_handle_v1 { + struct wlr_ext_workspace_manager_v1 *manager; + struct wlr_ext_workspace_group_handle_v1 *group; // May be NULL + char *id; + char *name; + struct wl_array coordinates; + uint32_t caps; // ext_workspace_handle_v1_workspace_capabilities + uint32_t state; // ext_workspace_handle_v1_state + + struct { + struct wl_signal destroy; + } events; + + struct wl_list link; // wlr_ext_workspace_manager_v1.workspaces + + void *data; + + struct { + struct wl_list resources; // wlr_ext_workspace_v1_resource.link + } WLR_PRIVATE; +}; + +struct wlr_ext_workspace_manager_v1 *wlr_ext_workspace_manager_v1_create( + struct wl_display *display, uint32_t version); + +struct wlr_ext_workspace_group_handle_v1 *wlr_ext_workspace_group_handle_v1_create( + struct wlr_ext_workspace_manager_v1 *manager, uint32_t caps); +void wlr_ext_workspace_group_handle_v1_destroy( + struct wlr_ext_workspace_group_handle_v1 *group); + +void wlr_ext_workspace_group_handle_v1_output_enter( + struct wlr_ext_workspace_group_handle_v1 *group, struct wlr_output *output); +void wlr_ext_workspace_group_handle_v1_output_leave( + struct wlr_ext_workspace_group_handle_v1 *group, struct wlr_output *output); + +struct wlr_ext_workspace_handle_v1 *wlr_ext_workspace_handle_v1_create( + struct wlr_ext_workspace_manager_v1 *manager, const char *id, uint32_t caps); +void wlr_ext_workspace_handle_v1_destroy(struct wlr_ext_workspace_handle_v1 *workspace); + +void wlr_ext_workspace_handle_v1_set_group( + struct wlr_ext_workspace_handle_v1 *workspace, + struct wlr_ext_workspace_group_handle_v1 *group); +void wlr_ext_workspace_handle_v1_set_name( + struct wlr_ext_workspace_handle_v1 *workspace, const char *name); +void wlr_ext_workspace_handle_v1_set_coordinates( + struct wlr_ext_workspace_handle_v1 *workspace, + const uint32_t *coords, size_t coords_len); +void wlr_ext_workspace_handle_v1_set_active( + struct wlr_ext_workspace_handle_v1 *workspace, bool enabled); +void wlr_ext_workspace_handle_v1_set_urgent( + struct wlr_ext_workspace_handle_v1 *workspace, bool enabled); +void wlr_ext_workspace_handle_v1_set_hidden( + struct wlr_ext_workspace_handle_v1 *workspace, bool enabled); + +#endif diff --git a/protocol/meson.build b/protocol/meson.build index 0283dad64..30aeb68c6 100644 --- a/protocol/meson.build +++ b/protocol/meson.build @@ -36,6 +36,7 @@ protocols = { 'ext-image-copy-capture-v1': wl_protocol_dir / 'staging/ext-image-copy-capture/ext-image-copy-capture-v1.xml', 'ext-session-lock-v1': wl_protocol_dir / 'staging/ext-session-lock/ext-session-lock-v1.xml', 'ext-data-control-v1': wl_protocol_dir / 'staging/ext-data-control/ext-data-control-v1.xml', + 'ext-workspace-v1': wl_protocol_dir / 'staging/ext-workspace/ext-workspace-v1.xml', 'fractional-scale-v1': wl_protocol_dir / 'staging/fractional-scale/fractional-scale-v1.xml', 'linux-drm-syncobj-v1': wl_protocol_dir / 'staging/linux-drm-syncobj/linux-drm-syncobj-v1.xml', 'security-context-v1': wl_protocol_dir / 'staging/security-context/security-context-v1.xml', diff --git a/types/meson.build b/types/meson.build index 402fd3e11..26958f048 100644 --- a/types/meson.build +++ b/types/meson.build @@ -51,6 +51,7 @@ wlr_files += files( 'wlr_ext_data_control_v1.c', 'wlr_ext_foreign_toplevel_list_v1.c', 'wlr_ext_image_copy_capture_v1.c', + 'wlr_ext_workspace_v1.c', 'wlr_fixes.c', 'wlr_foreign_toplevel_management_v1.c', 'wlr_fractional_scale_v1.c', diff --git a/types/wlr_ext_workspace_v1.c b/types/wlr_ext_workspace_v1.c new file mode 100644 index 000000000..7aaf80338 --- /dev/null +++ b/types/wlr_ext_workspace_v1.c @@ -0,0 +1,944 @@ +#include +#include +#include +#include +#include +#include "ext-workspace-v1-protocol.h" + +#define EXT_WORKSPACE_V1_VERSION 1 + +struct wlr_ext_workspace_v1_group_output { + struct wlr_output *output; + struct wlr_ext_workspace_group_handle_v1 *group; + struct wl_listener output_bind; + struct wl_listener output_destroy; + struct wl_list link; +}; + +// These structs wrap wl_resource of each interface to access the request queue +// (wlr_ext_workspace_manager_v1_resource.requests) assigned per manager resource + +struct wlr_ext_workspace_manager_v1_resource { + struct wl_resource *resource; + struct wlr_ext_workspace_manager_v1 *manager; + struct wl_list requests; // wlr_ext_workspace_v1_request.link + struct wl_list workspace_resources; // wlr_ext_workspace_v1_resource.link + struct wl_list group_resources; // wlr_ext_workspace_group_v1_resource.link + struct wl_list link; // wlr_ext_workspace_manager_v1.resources +}; + +struct wlr_ext_workspace_group_v1_resource { + struct wl_resource *resource; + struct wlr_ext_workspace_group_handle_v1 *group; + struct wlr_ext_workspace_manager_v1_resource *manager; + struct wl_list link; // wlr_ext_workspace_group_v1.resources + struct wl_list manager_resource_link; // wlr_ext_workspace_manager_v1_resource.group_resources +}; + +struct wlr_ext_workspace_v1_resource { + struct wl_resource *resource; + struct wlr_ext_workspace_handle_v1 *workspace; + struct wlr_ext_workspace_manager_v1_resource *manager; + struct wl_list link; // wlr_ext_workspace_v1.resources + struct wl_list manager_resource_link; // wlr_ext_workspace_manager_v1_resource.workspace_resources +}; + +static const struct ext_workspace_group_handle_v1_interface group_impl; + +static struct wlr_ext_workspace_group_v1_resource *group_resource_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &ext_workspace_group_handle_v1_interface, &group_impl)); + return wl_resource_get_user_data(resource); +} + +static const struct ext_workspace_handle_v1_interface workspace_impl; + +static struct wlr_ext_workspace_v1_resource *workspace_resource_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &ext_workspace_handle_v1_interface, &workspace_impl)); + return wl_resource_get_user_data(resource); +} + +static const struct ext_workspace_manager_v1_interface manager_impl; + +static struct wlr_ext_workspace_manager_v1_resource *manager_resource_from_resource( + struct wl_resource *resource) { + assert(wl_resource_instance_of(resource, + &ext_workspace_manager_v1_interface, &manager_impl)); + return wl_resource_get_user_data(resource); +} + +static void workspace_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static void workspace_handle_activate(struct wl_client *client, + struct wl_resource *workspace_resource) { + struct wlr_ext_workspace_v1_resource *workspace_res = + workspace_resource_from_resource(workspace_resource); + if (!workspace_res) { + return; + } + + struct wlr_ext_workspace_v1_request *req = calloc(1, sizeof(*req)); + if (!req) { + wl_resource_post_no_memory(workspace_resource); + return; + } + req->type = WLR_EXT_WORKSPACE_V1_REQUEST_ACTIVATE; + req->activate.workspace = workspace_res->workspace; + wl_list_insert(workspace_res->manager->requests.prev, &req->link); +} + +static void workspace_handle_deactivate(struct wl_client *client, + struct wl_resource *workspace_resource) { + struct wlr_ext_workspace_v1_resource *workspace_res = + workspace_resource_from_resource(workspace_resource); + if (!workspace_res) { + return; + } + + struct wlr_ext_workspace_v1_request *req = calloc(1, sizeof(*req)); + if (!req) { + wl_resource_post_no_memory(workspace_resource); + return; + } + req->type = WLR_EXT_WORKSPACE_V1_REQUEST_DEACTIVATE; + req->deactivate.workspace = workspace_res->workspace; + wl_list_insert(workspace_res->manager->requests.prev, &req->link); +} + +static void workspace_handle_assign(struct wl_client *client, + struct wl_resource *workspace_resource, + struct wl_resource *group_resource) { + struct wlr_ext_workspace_v1_resource *workspace_res = + workspace_resource_from_resource(workspace_resource); + struct wlr_ext_workspace_group_v1_resource *group_res = + group_resource_from_resource(group_resource); + if (!workspace_res || !group_res) { + return; + } + + struct wlr_ext_workspace_v1_request *req = calloc(1, sizeof(*req)); + if (!req) { + wl_resource_post_no_memory(workspace_resource); + return; + } + req->type = WLR_EXT_WORKSPACE_V1_REQUEST_ASSIGN; + req->assign.group = group_res->group; + req->assign.workspace = workspace_res->workspace; + wl_list_insert(workspace_res->manager->requests.prev, &req->link); +} + +static void workspace_handle_remove(struct wl_client *client, + struct wl_resource *workspace_resource) { + struct wlr_ext_workspace_v1_resource *workspace_res = + workspace_resource_from_resource(workspace_resource); + if (!workspace_res) { + return; + } + + struct wlr_ext_workspace_v1_request *req = calloc(1, sizeof(*req)); + if (!req) { + wl_resource_post_no_memory(workspace_resource); + return; + } + req->type = WLR_EXT_WORKSPACE_V1_REQUEST_REMOVE; + req->remove.workspace = workspace_res->workspace; + wl_list_insert(workspace_res->manager->requests.prev, &req->link); +} + +static const struct ext_workspace_handle_v1_interface workspace_impl = { + .destroy = workspace_handle_destroy, + .activate = workspace_handle_activate, + .deactivate = workspace_handle_deactivate, + .assign = workspace_handle_assign, + .remove = workspace_handle_remove, +}; + +static void group_handle_create_workspace(struct wl_client *client, + struct wl_resource *group_resource, const char *name) { + struct wlr_ext_workspace_group_v1_resource *group_res = + group_resource_from_resource(group_resource); + if (!group_res) { + return; + } + + struct wlr_ext_workspace_v1_request *req = calloc(1, sizeof(*req)); + if (!req) { + wl_resource_post_no_memory(group_resource); + return; + } + req->create_workspace.name = strdup(name); + if (!req->create_workspace.name) { + free(req); + wl_resource_post_no_memory(group_resource); + return; + } + req->type = WLR_EXT_WORKSPACE_V1_REQUEST_CREATE_WORKSPACE; + req->create_workspace.group = group_res->group; + wl_list_insert(group_res->manager->requests.prev, &req->link); +} + +static void group_handle_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +} + +static const struct ext_workspace_group_handle_v1_interface group_impl = { + .create_workspace = group_handle_create_workspace, + .destroy = group_handle_destroy, +}; + +static void destroy_workspace_resource( + struct wlr_ext_workspace_v1_resource *workspace_res) { + wl_list_remove(&workspace_res->link); + wl_list_remove(&workspace_res->manager_resource_link); + wl_resource_set_user_data(workspace_res->resource, NULL); + free(workspace_res); +} + +static void workspace_resource_destroy(struct wl_resource *resource) { + struct wlr_ext_workspace_v1_resource *workspace_res = + workspace_resource_from_resource(resource); + if (workspace_res) { + destroy_workspace_resource(workspace_res); + } +} + +static struct wlr_ext_workspace_v1_resource *create_workspace_resource( + struct wlr_ext_workspace_handle_v1 *workspace, + struct wlr_ext_workspace_manager_v1_resource *manager_res) { + struct wlr_ext_workspace_v1_resource *workspace_res = + calloc(1, sizeof(*workspace_res)); + if (!workspace_res) { + return NULL; + } + + struct wl_client *client = wl_resource_get_client(manager_res->resource); + workspace_res->resource = wl_resource_create(client, + &ext_workspace_handle_v1_interface, + wl_resource_get_version(manager_res->resource), 0); + if (!workspace_res->resource) { + free(workspace_res); + return NULL; + } + wl_resource_set_implementation(workspace_res->resource, + &workspace_impl, workspace_res, workspace_resource_destroy); + + workspace_res->workspace = workspace; + workspace_res->manager = manager_res; + wl_list_insert(&workspace->resources, &workspace_res->link); + wl_list_insert(&manager_res->workspace_resources, &workspace_res->manager_resource_link); + + return workspace_res; +} + +static void destroy_group_resource(struct wlr_ext_workspace_group_v1_resource *group_res) { + wl_list_remove(&group_res->link); + wl_list_remove(&group_res->manager_resource_link); + wl_resource_set_user_data(group_res->resource, NULL); + free(group_res); +} + +static void group_handle_resource_destroy(struct wl_resource *resource) { + struct wlr_ext_workspace_group_v1_resource *group_res = + group_resource_from_resource(resource); + if (group_res) { + destroy_group_resource(group_res); + } +} + +static struct wlr_ext_workspace_group_v1_resource *create_group_resource( + struct wlr_ext_workspace_group_handle_v1 *group, + struct wlr_ext_workspace_manager_v1_resource *manager_res) { + struct wlr_ext_workspace_group_v1_resource *group_res = + calloc(1, sizeof(*group_res)); + if (!group_res) { + return NULL; + } + + struct wl_client *client = wl_resource_get_client(manager_res->resource); + uint32_t version = wl_resource_get_version(manager_res->resource); + group_res->resource = wl_resource_create(client, + &ext_workspace_group_handle_v1_interface, version, 0); + if (group_res->resource == NULL) { + free(group_res); + return NULL; + } + wl_resource_set_implementation(group_res->resource, &group_impl, + group_res, group_handle_resource_destroy); + + group_res->group = group; + group_res->manager = manager_res; + wl_list_insert(&group->resources, &group_res->link); + wl_list_insert(&manager_res->group_resources, &group_res->manager_resource_link); + + return group_res; +} + +static void destroy_requests(struct wlr_ext_workspace_manager_v1_resource *manager_res) { + struct wlr_ext_workspace_v1_request *req, *tmp; + wl_list_for_each_safe(req, tmp, &manager_res->requests, link) { + if (req->type == WLR_EXT_WORKSPACE_V1_REQUEST_CREATE_WORKSPACE) { + free(req->create_workspace.name); + } + wl_list_remove(&req->link); + free(req); + } +} + +static void clear_requests_by(struct wlr_ext_workspace_manager_v1_resource *manager_res, + struct wlr_ext_workspace_group_handle_v1 *group, + struct wlr_ext_workspace_handle_v1 *workspace) { + struct wlr_ext_workspace_v1_request *req, *tmp; + wl_list_for_each_safe(req, tmp, &manager_res->requests, link) { + switch (req->type) { + case WLR_EXT_WORKSPACE_V1_REQUEST_CREATE_WORKSPACE: + if (group && req->create_workspace.group == group) { + req->create_workspace.group = NULL; + } + break; + case WLR_EXT_WORKSPACE_V1_REQUEST_ACTIVATE: + if (workspace && req->activate.workspace == workspace) { + req->activate.workspace = NULL; + } + break; + case WLR_EXT_WORKSPACE_V1_REQUEST_DEACTIVATE: + if (workspace && req->deactivate.workspace == workspace) { + req->deactivate.workspace = NULL; + } + break; + case WLR_EXT_WORKSPACE_V1_REQUEST_ASSIGN: + if (workspace && req->assign.workspace == workspace) { + req->assign.workspace = NULL; + } + if (group && req->assign.group == group) { + req->assign.group = NULL; + } + break; + case WLR_EXT_WORKSPACE_V1_REQUEST_REMOVE: + if (workspace && req->remove.workspace == workspace) { + req->remove.workspace = NULL; + } + break; + } + } +} + +static void manager_handle_commit(struct wl_client *client, struct wl_resource *resource) { + struct wlr_ext_workspace_manager_v1_resource *manager_res = + manager_resource_from_resource(resource); + if (!manager_res) { + return; + } + + struct wlr_ext_workspace_v1_commit_event commit_event = { + .requests = &manager_res->requests, + }; + wl_signal_emit_mutable(&manager_res->manager->events.commit, &commit_event); + destroy_requests(manager_res); +} + +static void handle_idle(void *data) { + struct wlr_ext_workspace_manager_v1 *manager = data; + + struct wlr_ext_workspace_manager_v1_resource *manager_res; + wl_list_for_each(manager_res, &manager->resources, link) { + ext_workspace_manager_v1_send_done(manager_res->resource); + } + manager->idle_source = NULL; +} + +static void manager_schedule_done(struct wlr_ext_workspace_manager_v1 *manager) { + if (!manager->idle_source) { + manager->idle_source = wl_event_loop_add_idle(manager->event_loop, + handle_idle, manager); + } +} + +static void workspace_send_details(struct wlr_ext_workspace_v1_resource *workspace_res) { + struct wlr_ext_workspace_handle_v1 *workspace = workspace_res->workspace; + struct wl_resource *resource = workspace_res->resource; + + ext_workspace_handle_v1_send_capabilities(resource, workspace->caps); + if (workspace->coordinates.size > 0) { + ext_workspace_handle_v1_send_coordinates(resource, &workspace->coordinates); + } + if (workspace->name) { + ext_workspace_handle_v1_send_name(resource, workspace->name); + } + if (workspace->id) { + ext_workspace_handle_v1_send_id(resource, workspace->id); + } + ext_workspace_handle_v1_send_state(resource, workspace->state); + manager_schedule_done(workspace->manager); +} + +static void manager_handle_stop(struct wl_client *client, struct wl_resource *resource) { + ext_workspace_manager_v1_send_finished(resource); + wl_resource_destroy(resource); +} + +static const struct ext_workspace_manager_v1_interface manager_impl = { + .commit = manager_handle_commit, + .stop = manager_handle_stop, +}; + +static void destroy_manager_resource(struct wlr_ext_workspace_manager_v1_resource *manager_res) { + destroy_requests(manager_res); + + struct wlr_ext_workspace_v1_resource *workspace_res, *tmp2; + wl_list_for_each_safe(workspace_res, tmp2, + &manager_res->workspace_resources, manager_resource_link) { + destroy_workspace_resource(workspace_res); + } + struct wlr_ext_workspace_group_v1_resource *group_res, *tmp3; + wl_list_for_each_safe(group_res, tmp3, + &manager_res->group_resources, manager_resource_link) { + destroy_group_resource(group_res); + } + + wl_list_remove(&manager_res->link); + wl_resource_set_user_data(manager_res->resource, NULL); + free(manager_res); +} + +static void manager_resource_destroy(struct wl_resource *resource) { + struct wlr_ext_workspace_manager_v1_resource *manager_res = + manager_resource_from_resource(resource); + if (manager_res) { + destroy_manager_resource(manager_res); + } +} + +static void group_send_details(struct wlr_ext_workspace_group_v1_resource *group_res) { + struct wlr_ext_workspace_group_handle_v1 *group = group_res->group; + struct wl_resource *resource = group_res->resource; + struct wl_client *client = wl_resource_get_client(resource); + + ext_workspace_group_handle_v1_send_capabilities(resource, group->caps); + + struct wlr_ext_workspace_v1_group_output *group_output; + wl_list_for_each(group_output, &group->outputs, link) { + struct wl_resource *output_resource; + wl_resource_for_each(output_resource, &group_output->output->resources) { + if (wl_resource_get_client(output_resource) == client) { + ext_workspace_group_handle_v1_send_output_enter( + resource, output_resource); + } + } + } + + manager_schedule_done(group->manager); +} + +static void manager_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) { + struct wlr_ext_workspace_manager_v1 *manager = data; + + struct wlr_ext_workspace_manager_v1_resource *manager_res = + calloc(1, sizeof(*manager_res)); + if (!manager_res) { + wl_client_post_no_memory(client); + return; + } + + manager_res->manager = manager; + wl_list_init(&manager_res->requests); + wl_list_init(&manager_res->workspace_resources); + wl_list_init(&manager_res->group_resources); + + manager_res->resource = wl_resource_create(client, + &ext_workspace_manager_v1_interface, version, id); + if (!manager_res->resource) { + free(manager_res); + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(manager_res->resource, &manager_impl, + manager_res, manager_resource_destroy); + wl_list_insert(&manager->resources, &manager_res->link); + + struct wlr_ext_workspace_group_handle_v1 *group; + wl_list_for_each(group, &manager->groups, link) { + struct wlr_ext_workspace_group_v1_resource *group_res = + create_group_resource(group, manager_res); + if (!group_res) { + wl_resource_post_no_memory(manager_res->resource); + continue; + } + ext_workspace_manager_v1_send_workspace_group( + manager_res->resource, group_res->resource); + group_send_details(group_res); + } + + struct wlr_ext_workspace_handle_v1 *workspace; + wl_list_for_each(workspace, &manager->workspaces, link) { + struct wlr_ext_workspace_v1_resource *workspace_res = + create_workspace_resource(workspace, manager_res); + if (!workspace_res) { + wl_resource_post_no_memory(manager_res->resource); + continue; + } + ext_workspace_manager_v1_send_workspace( + manager_res->resource, workspace_res->resource); + workspace_send_details(workspace_res); + + if (!workspace->group) { + continue; + } + struct wlr_ext_workspace_group_v1_resource *group_res; + wl_list_for_each(group_res, &workspace->group->resources, link) { + if (group_res->manager == manager_res) { + ext_workspace_group_handle_v1_send_workspace_enter( + group_res->resource, workspace_res->resource); + } + } + } + + ext_workspace_manager_v1_send_done(manager_res->resource); +} + +static void manager_handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_ext_workspace_manager_v1 *manager = + wl_container_of(listener, manager, display_destroy); + + wl_signal_emit_mutable(&manager->events.destroy, NULL); + assert(wl_list_empty(&manager->events.commit.listener_list)); + assert(wl_list_empty(&manager->events.destroy.listener_list)); + + struct wlr_ext_workspace_group_handle_v1 *group, *tmp; + wl_list_for_each_safe(group, tmp, &manager->groups, link) { + wlr_ext_workspace_group_handle_v1_destroy(group); + } + + struct wlr_ext_workspace_handle_v1 *workspace, *tmp2; + wl_list_for_each_safe(workspace, tmp2, &manager->workspaces, link) { + wlr_ext_workspace_handle_v1_destroy(workspace); + } + + struct wlr_ext_workspace_manager_v1_resource *manager_res, *tmp3; + wl_list_for_each_safe(manager_res, tmp3, &manager->resources, link) { + destroy_manager_resource(manager_res); + } + + if (manager->idle_source) { + wl_event_source_remove(manager->idle_source); + } + + wl_list_remove(&manager->display_destroy.link); + wl_global_destroy(manager->global); + free(manager); +} + +struct wlr_ext_workspace_manager_v1 *wlr_ext_workspace_manager_v1_create( + struct wl_display *display, uint32_t version) { + assert(version <= EXT_WORKSPACE_V1_VERSION); + + struct wlr_ext_workspace_manager_v1 *manager = calloc(1, sizeof(*manager)); + if (!manager) { + return NULL; + } + + manager->global = wl_global_create(display, + &ext_workspace_manager_v1_interface, + version, manager, manager_bind); + if (!manager->global) { + free(manager); + return NULL; + } + + manager->event_loop = wl_display_get_event_loop(display); + + manager->display_destroy.notify = manager_handle_display_destroy; + wl_display_add_destroy_listener(display, &manager->display_destroy); + + wl_list_init(&manager->groups); + wl_list_init(&manager->workspaces); + wl_list_init(&manager->resources); + wl_signal_init(&manager->events.commit); + wl_signal_init(&manager->events.destroy); + + return manager; +} + +struct wlr_ext_workspace_group_handle_v1 *wlr_ext_workspace_group_handle_v1_create( + struct wlr_ext_workspace_manager_v1 *manager, uint32_t caps) { + struct wlr_ext_workspace_group_handle_v1 *group = calloc(1, sizeof(*group)); + if (!group) { + return NULL; + } + + group->manager = manager; + group->caps = caps; + + wl_list_init(&group->outputs); + wl_list_init(&group->resources); + wl_signal_init(&group->events.destroy); + + wl_list_insert(manager->groups.prev, &group->link); + + struct wlr_ext_workspace_manager_v1_resource *manager_res; + wl_list_for_each(manager_res, &manager->resources, link) { + struct wlr_ext_workspace_group_v1_resource *group_res = + create_group_resource(group, manager_res); + if (!group_res) { + continue; + } + ext_workspace_manager_v1_send_workspace_group( + manager_res->resource, group_res->resource); + group_send_details(group_res); + } + + manager_schedule_done(manager); + + return group; +} + +static void workspace_send_group(struct wlr_ext_workspace_handle_v1 *workspace, + struct wlr_ext_workspace_group_handle_v1 *group, bool enter) { + + struct wlr_ext_workspace_v1_resource *workspace_res; + wl_list_for_each(workspace_res, &workspace->resources, link) { + struct wlr_ext_workspace_group_v1_resource *group_res; + wl_list_for_each(group_res, &group->resources, link) { + if (group_res->manager != workspace_res->manager) { + continue; + } + if (enter) { + ext_workspace_group_handle_v1_send_workspace_enter( + group_res->resource, workspace_res->resource); + } else { + ext_workspace_group_handle_v1_send_workspace_leave( + group_res->resource, workspace_res->resource); + } + } + } + + manager_schedule_done(workspace->manager); +} + +static void destroy_group_output(struct wlr_ext_workspace_v1_group_output *group_output) { + wl_list_remove(&group_output->output_bind.link); + wl_list_remove(&group_output->output_destroy.link); + wl_list_remove(&group_output->link); + free(group_output); +} + +static void group_send_output(struct wlr_ext_workspace_group_handle_v1 *group, + struct wlr_output *output, bool enter) { + + struct wlr_ext_workspace_group_v1_resource *group_res; + wl_list_for_each(group_res, &group->resources, link) { + struct wl_client *client = + wl_resource_get_client(group_res->resource); + + struct wl_resource *output_resource; + wl_resource_for_each(output_resource, &output->resources) { + if (wl_resource_get_client(output_resource) != client) { + continue; + } + if (enter) { + ext_workspace_group_handle_v1_send_output_enter( + group_res->resource, output_resource); + } else { + ext_workspace_group_handle_v1_send_output_leave( + group_res->resource, output_resource); + } + } + } + + manager_schedule_done(group->manager); +} + +void wlr_ext_workspace_group_handle_v1_destroy( + struct wlr_ext_workspace_group_handle_v1 *group) { + if (!group) { + return; + } + + wl_signal_emit_mutable(&group->events.destroy, NULL); + + assert(wl_list_empty(&group->events.destroy.listener_list)); + + struct wlr_ext_workspace_handle_v1 *workspace; + wl_list_for_each(workspace, &group->manager->workspaces, link) { + if (workspace->group == group) { + workspace_send_group(workspace, group, false); + workspace->group = NULL; + } + } + + struct wlr_ext_workspace_group_v1_resource *group_res, *tmp; + wl_list_for_each_safe(group_res, tmp, &group->resources, link) { + ext_workspace_group_handle_v1_send_removed(group_res->resource); + destroy_group_resource(group_res); + } + + struct wlr_ext_workspace_manager_v1_resource *manager_res; + wl_list_for_each(manager_res, &group->manager->resources, link) { + clear_requests_by(manager_res, group, NULL); + } + + struct wlr_ext_workspace_v1_group_output *group_output, *tmp3; + wl_list_for_each_safe(group_output, tmp3, &group->outputs, link) { + group_send_output(group, group_output->output, false); + destroy_group_output(group_output); + } + + manager_schedule_done(group->manager); + + wl_list_remove(&group->link); + free(group); +} + +static void handle_output_bind(struct wl_listener *listener, void *data) { + struct wlr_ext_workspace_v1_group_output *group_output = + wl_container_of(listener, group_output, output_bind); + struct wlr_output_event_bind *event = data; + struct wl_client *client = wl_resource_get_client(event->resource); + + struct wlr_ext_workspace_group_v1_resource *group_res; + wl_list_for_each(group_res, &group_output->group->resources, link) { + if (wl_resource_get_client(group_res->resource) == client) { + ext_workspace_group_handle_v1_send_output_enter( + group_res->resource, event->resource); + } + } + + manager_schedule_done(group_output->group->manager); +} + +static void handle_output_destroy(struct wl_listener *listener, void *data) { + struct wlr_ext_workspace_v1_group_output *group_output = + wl_container_of(listener, group_output, output_destroy); + group_send_output(group_output->group, group_output->output, false); + destroy_group_output(group_output); +} + +static struct wlr_ext_workspace_v1_group_output *get_group_output( + struct wlr_ext_workspace_group_handle_v1 *group, + struct wlr_output *output) { + struct wlr_ext_workspace_v1_group_output *group_output; + wl_list_for_each(group_output, &group->outputs, link) { + if (group_output->output == output) { + return group_output; + } + } + return NULL; +} + +void wlr_ext_workspace_group_handle_v1_output_enter( + struct wlr_ext_workspace_group_handle_v1 *group, + struct wlr_output *output) { + if (get_group_output(group, output)) { + return; + } + struct wlr_ext_workspace_v1_group_output *group_output = + calloc(1, sizeof(*group_output)); + if (!group_output) { + return; + } + group_output->output = output; + group_output->group = group; + wl_list_insert(&group->outputs, &group_output->link); + + group_output->output_bind.notify = handle_output_bind; + wl_signal_add(&output->events.bind, &group_output->output_bind); + group_output->output_destroy.notify = handle_output_destroy; + wl_signal_add(&output->events.destroy, &group_output->output_destroy); + + group_send_output(group, output, true); +} + +void wlr_ext_workspace_group_handle_v1_output_leave( + struct wlr_ext_workspace_group_handle_v1 *group, + struct wlr_output *output) { + struct wlr_ext_workspace_v1_group_output *group_output = + get_group_output(group, output); + if (!group_output) { + return; + } + + group_send_output(group, output, false); + destroy_group_output(group_output); +} + +struct wlr_ext_workspace_handle_v1 *wlr_ext_workspace_handle_v1_create( + struct wlr_ext_workspace_manager_v1 *manager, const char *id, uint32_t caps) { + struct wlr_ext_workspace_handle_v1 *workspace = calloc(1, sizeof(*workspace)); + if (!workspace) { + return NULL; + } + + workspace->manager = manager; + workspace->caps = caps; + + if (id) { + workspace->id = strdup(id); + if (!workspace->id) { + free(workspace); + return NULL; + } + } + + wl_list_init(&workspace->resources); + wl_array_init(&workspace->coordinates); + wl_signal_init(&workspace->events.destroy); + + wl_list_insert(&manager->workspaces, &workspace->link); + + struct wlr_ext_workspace_manager_v1_resource *manager_res; + wl_list_for_each(manager_res, &manager->resources, link) { + struct wlr_ext_workspace_v1_resource *workspace_res = + create_workspace_resource(workspace, manager_res); + if (!workspace_res) { + continue; + } + ext_workspace_manager_v1_send_workspace( + manager_res->resource, workspace_res->resource); + workspace_send_details(workspace_res); + } + + manager_schedule_done(manager); + + return workspace; +} + +void wlr_ext_workspace_handle_v1_destroy(struct wlr_ext_workspace_handle_v1 *workspace) { + if (!workspace) { + return; + } + + wl_signal_emit_mutable(&workspace->events.destroy, NULL); + + assert(wl_list_empty(&workspace->events.destroy.listener_list)); + + if (workspace->group) { + workspace_send_group(workspace, workspace->group, false); + } + + struct wlr_ext_workspace_v1_resource *workspace_res, *tmp; + wl_list_for_each_safe(workspace_res, tmp, &workspace->resources, link) { + ext_workspace_handle_v1_send_removed(workspace_res->resource); + destroy_workspace_resource(workspace_res); + } + + struct wlr_ext_workspace_manager_v1_resource *manager_res; + wl_list_for_each(manager_res, &workspace->manager->resources, link) { + clear_requests_by(manager_res, NULL, workspace); + } + + manager_schedule_done(workspace->manager); + + wl_list_remove(&workspace->link); + wl_array_release(&workspace->coordinates); + free(workspace->id); + free(workspace->name); + free(workspace); +} + +void wlr_ext_workspace_handle_v1_set_group( + struct wlr_ext_workspace_handle_v1 *workspace, + struct wlr_ext_workspace_group_handle_v1 *group) { + if (workspace->group == group) { + return; + } + + if (workspace->group) { + workspace_send_group(workspace, workspace->group, false); + } + workspace->group = group; + if (group) { + workspace_send_group(workspace, group, true); + } +} + +void wlr_ext_workspace_handle_v1_set_name( + struct wlr_ext_workspace_handle_v1 *workspace, const char *name) { + assert(name); + + if (workspace->name && strcmp(workspace->name, name) == 0) { + return; + } + + free(workspace->name); + workspace->name = strdup(name); + if (workspace->name == NULL) { + return; + } + + struct wlr_ext_workspace_v1_resource *workspace_res; + wl_list_for_each(workspace_res, &workspace->resources, link) { + ext_workspace_handle_v1_send_name( + workspace_res->resource, workspace->name); + } + + manager_schedule_done(workspace->manager); +} + +void wlr_ext_workspace_handle_v1_set_coordinates( + struct wlr_ext_workspace_handle_v1 *workspace, + const uint32_t *coords, size_t coords_len) { + size_t size = coords_len * sizeof(coords[0]); + if (size == workspace->coordinates.size && (size == 0 || + memcmp(workspace->coordinates.data, coords, size) == 0)) { + return; + } + + wl_array_release(&workspace->coordinates); + wl_array_init(&workspace->coordinates); + struct wl_array arr = { + .data = (void *)coords, + .size = size, + }; + wl_array_copy(&workspace->coordinates, &arr); + + struct wlr_ext_workspace_v1_resource *workspace_res; + wl_list_for_each(workspace_res, &workspace->resources, link) { + ext_workspace_handle_v1_send_coordinates( + workspace_res->resource, &workspace->coordinates); + } + + manager_schedule_done(workspace->manager); +} + +static void workspace_set_state(struct wlr_ext_workspace_handle_v1 *workspace, + enum ext_workspace_handle_v1_state state, bool enabled) { + uint32_t old_state = workspace->state; + if (enabled) { + workspace->state |= state; + } else { + workspace->state &= ~state; + } + if (old_state == workspace->state) { + return; + } + + struct wlr_ext_workspace_v1_resource *workspace_res; + wl_list_for_each(workspace_res, &workspace->resources, link) { + ext_workspace_handle_v1_send_state( + workspace_res->resource, workspace->state); + } + + manager_schedule_done(workspace->manager); +} + +void wlr_ext_workspace_handle_v1_set_active( + struct wlr_ext_workspace_handle_v1 *workspace, bool enabled) { + workspace_set_state(workspace, EXT_WORKSPACE_HANDLE_V1_STATE_ACTIVE, enabled); +} + +void wlr_ext_workspace_handle_v1_set_urgent( + struct wlr_ext_workspace_handle_v1 *workspace, bool enabled) { + workspace_set_state(workspace, EXT_WORKSPACE_HANDLE_V1_STATE_URGENT, enabled); +} + +void wlr_ext_workspace_handle_v1_set_hidden( + struct wlr_ext_workspace_handle_v1 *workspace, bool enabled) { + workspace_set_state(workspace, EXT_WORKSPACE_HANDLE_V1_STATE_HIDDEN, enabled); +} From d6c7a161c778c9d6e40efaee71993ca96f0048f0 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sun, 23 Nov 2025 22:36:32 +0100 Subject: [PATCH 235/311] color_representation_v1: send chroma_location protocol error This error has been added in: https://gitlab.freedesktop.org/wayland/wayland-protocols/-/commit/46f46863b7b5ac8d9c5bcdb167b57ae803d064ac --- types/wlr_color_representation_v1.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/types/wlr_color_representation_v1.c b/types/wlr_color_representation_v1.c index ff9b6e3b7..a447d77a7 100644 --- a/types/wlr_color_representation_v1.c +++ b/types/wlr_color_representation_v1.c @@ -178,11 +178,7 @@ static void color_repr_handle_set_chroma_location(struct wl_client *client, uint32_t version = wl_resource_get_version(resource); if (!wp_color_representation_surface_v1_chroma_location_is_valid( version, chroma_location)) { - wlr_log(WLR_ERROR, "Client sent chroma location which isn't a valid enum value"); - // TODO: Post actual error once - // https://gitlab.freedesktop.org/wayland/wayland-protocols/-/merge_requests/429 - // is merged and wlroots depends on a new enough wayland-protocols. - wl_client_post_implementation_error(resource->client, + wl_resource_post_error(resource, WP_COLOR_REPRESENTATION_SURFACE_V1_ERROR_CHROMA_LOCATION, "Chroma location is not a valid enum value"); return; } From 4477ca7d3223b53b45de1b80ff2f39ac31271e56 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sun, 1 Feb 2026 21:13:18 +0100 Subject: [PATCH 236/311] build: bump version to 0.20.0-rc1 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index e602bb427..5b8f12073 100644 --- a/meson.build +++ b/meson.build @@ -1,7 +1,7 @@ project( 'wlroots', 'c', - version: '0.20.0-dev', + version: '0.20.0-rc1', license: 'MIT', meson_version: '>=1.3', default_options: [ From 7f87e258b2bcfd3ec85aceeb25ef9a51fbd59a19 Mon Sep 17 00:00:00 2001 From: liupeng Date: Mon, 2 Feb 2026 09:11:41 +0800 Subject: [PATCH 237/311] render/drm_syncobj: drop unnecessary drmSyncobjTimelineWait() arg Signed-off-by: liupeng --- render/drm_syncobj.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/render/drm_syncobj.c b/render/drm_syncobj.c index 912a2b6e5..38541ca98 100644 --- a/render/drm_syncobj.c +++ b/render/drm_syncobj.c @@ -167,8 +167,7 @@ bool wlr_drm_syncobj_timeline_check(struct wlr_drm_syncobj_timeline *timeline, etime = ETIME; #endif - uint32_t signaled_point; - int ret = drmSyncobjTimelineWait(timeline->drm_fd, &timeline->handle, &point, 1, 0, flags, &signaled_point); + int ret = drmSyncobjTimelineWait(timeline->drm_fd, &timeline->handle, &point, 1, 0, flags, NULL); if (ret != 0 && ret != -etime) { wlr_log_errno(WLR_ERROR, "drmSyncobjWait() failed"); return false; From 12c9502edfa52186248d75ae4187d268d8fd165f Mon Sep 17 00:00:00 2001 From: liupeng Date: Mon, 2 Feb 2026 09:12:39 +0800 Subject: [PATCH 238/311] render/drm_syncobj: fix function name in drmSyncobjTimelineWait() error log Signed-off-by: liupeng --- render/drm_syncobj.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/render/drm_syncobj.c b/render/drm_syncobj.c index 38541ca98..e1a407a1e 100644 --- a/render/drm_syncobj.c +++ b/render/drm_syncobj.c @@ -169,7 +169,7 @@ bool wlr_drm_syncobj_timeline_check(struct wlr_drm_syncobj_timeline *timeline, int ret = drmSyncobjTimelineWait(timeline->drm_fd, &timeline->handle, &point, 1, 0, flags, NULL); if (ret != 0 && ret != -etime) { - wlr_log_errno(WLR_ERROR, "drmSyncobjWait() failed"); + wlr_log_errno(WLR_ERROR, "drmSyncobjTimelineWait() failed"); return false; } From 4fe51aa43982cafc3881b1380d7b491d245d40f5 Mon Sep 17 00:00:00 2001 From: rewine Date: Sat, 31 Jan 2026 17:59:31 +0800 Subject: [PATCH 239/311] types: Simplify wlr_keyboard_group_destroy If the wlr_keyboard_group_remove_keyboard function is expanded, the code is equivalent to: ``` wl_list_for_each_safe(device, tmp_device, &group->devices, link) { struct wlr_keyboard_group *_group = group; struct wlr_keyboard *_keyboard = device->keyboard; struct keyboard_group_device *_device, *_tmp; wl_list_for_each_safe(_device, _tmp, &_group->devices, link) { if (_device->keyboard == _keyboard) { remove_keyboard_group_device(_device); continue; } } } ``` It's just running one more loop meaninglessly. --- types/wlr_keyboard_group.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/wlr_keyboard_group.c b/types/wlr_keyboard_group.c index 0ff6d93bb..6005ba647 100644 --- a/types/wlr_keyboard_group.c +++ b/types/wlr_keyboard_group.c @@ -308,7 +308,7 @@ void wlr_keyboard_group_remove_keyboard(struct wlr_keyboard_group *group, void wlr_keyboard_group_destroy(struct wlr_keyboard_group *group) { struct keyboard_group_device *device, *tmp_device; wl_list_for_each_safe(device, tmp_device, &group->devices, link) { - wlr_keyboard_group_remove_keyboard(group, device->keyboard); + remove_keyboard_group_device(device); } // Now group->keys might not be empty if a wlr_keyboard has emitted From 98196bbd898234203a177a4e4ca553604d6ad588 Mon Sep 17 00:00:00 2001 From: rewine Date: Tue, 3 Feb 2026 21:24:32 +0800 Subject: [PATCH 240/311] wlr_cursor: add comments for signal parameters --- include/wlr/types/wlr_cursor.h | 40 +++++++++++++++++----------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/include/wlr/types/wlr_cursor.h b/include/wlr/types/wlr_cursor.h index 041f735cf..aaa18a437 100644 --- a/include/wlr/types/wlr_cursor.h +++ b/include/wlr/types/wlr_cursor.h @@ -49,30 +49,30 @@ struct wlr_cursor { * your responsibility. */ struct { - struct wl_signal motion; - struct wl_signal motion_absolute; - struct wl_signal button; - struct wl_signal axis; + struct wl_signal motion; // struct wlr_pointer_motion_event + struct wl_signal motion_absolute; // struct wlr_pointer_motion_absolute_event + struct wl_signal button; // struct wlr_pointer_button_event + struct wl_signal axis; // struct wlr_pointer_axis_event struct wl_signal frame; - struct wl_signal swipe_begin; - struct wl_signal swipe_update; - struct wl_signal swipe_end; - struct wl_signal pinch_begin; - struct wl_signal pinch_update; - struct wl_signal pinch_end; - struct wl_signal hold_begin; - struct wl_signal hold_end; + struct wl_signal swipe_begin; // struct wlr_pointer_swipe_begin_event + struct wl_signal swipe_update; // struct wlr_pointer_swipe_update_event + struct wl_signal swipe_end; // struct wlr_pointer_swipe_end_event + struct wl_signal pinch_begin; // struct wlr_pointer_pinch_begin_event + struct wl_signal pinch_update; // struct wlr_pointer_pinch_update_event + struct wl_signal pinch_end; // struct wlr_pointer_pinch_end_event + struct wl_signal hold_begin; // struct wlr_pointer_hold_begin_event + struct wl_signal hold_end; // struct wlr_pointer_hold_end_event - struct wl_signal touch_up; - struct wl_signal touch_down; - struct wl_signal touch_motion; - struct wl_signal touch_cancel; + struct wl_signal touch_up; // struct wlr_touch_up_event + struct wl_signal touch_down; // struct wlr_touch_down_event + struct wl_signal touch_motion; // struct wlr_touch_motion_event + struct wl_signal touch_cancel; // struct wlr_touch_cancel_event struct wl_signal touch_frame; - struct wl_signal tablet_tool_axis; - struct wl_signal tablet_tool_proximity; - struct wl_signal tablet_tool_tip; - struct wl_signal tablet_tool_button; + struct wl_signal tablet_tool_axis; // struct wlr_tablet_tool_axis_event + struct wl_signal tablet_tool_proximity; // struct wlr_tablet_tool_proximity_event + struct wl_signal tablet_tool_tip; // struct wlr_tablet_tool_tip_event + struct wl_signal tablet_tool_button; // struct wlr_tablet_tool_button_event } events; void *data; From 7cb4e30bfdc3f1043867aaac667ca5c47d2d1cbd Mon Sep 17 00:00:00 2001 From: rewine Date: Tue, 3 Feb 2026 21:25:50 +0800 Subject: [PATCH 241/311] wlr_cursor: fix event type in handle_tablet_tool_button --- types/wlr_cursor.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/wlr_cursor.c b/types/wlr_cursor.c index f2269db0f..8d0d77475 100644 --- a/types/wlr_cursor.c +++ b/types/wlr_cursor.c @@ -956,7 +956,7 @@ static void handle_tablet_tool_axis(struct wl_listener *listener, void *data) { static void handle_tablet_tool_button(struct wl_listener *listener, void *data) { - struct wlr_tablet_tool_button *event = data; + struct wlr_tablet_tool_button_event *event = data; struct wlr_cursor_device *device; device = wl_container_of(listener, device, tablet_tool_button); wl_signal_emit_mutable(&device->cursor->events.tablet_tool_button, event); From 90f9f59041b9d02809d4d64f45ca6ff2e61a722e Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Wed, 4 Feb 2026 00:11:35 +0100 Subject: [PATCH 242/311] xwayland: try flushing immediately in xwm_schedule_flush() wl_event_source_fd_update() goes through another event loop cycle, which delays writes. This extra cycle was introduced in 6ada67da9bb0 ("xwayland/xwm: implement somewhat asynchronous request flushing"). Try flushing the X11 WM FD immediately if we can. References: https://gitlab.freedesktop.org/wlroots/wlroots/-/issues/4044 --- xwayland/xwm.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/xwayland/xwm.c b/xwayland/xwm.c index c8eac2ce1..e44a11743 100644 --- a/xwayland/xwm.c +++ b/xwayland/xwm.c @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -2881,5 +2882,20 @@ xcb_connection_t *wlr_xwayland_get_xwm_connection( } void xwm_schedule_flush(struct wlr_xwm *xwm) { + struct pollfd pollfd = { + .fd = xcb_get_file_descriptor(xwm->xcb_conn), + .events = POLLOUT, + }; + if (poll(&pollfd, 1, 0) < 0) { + wlr_log(WLR_ERROR, "poll() failed"); + return; + } + + // If we can write immediately, do so + if (pollfd.revents & POLLOUT) { + xcb_flush(xwm->xcb_conn); + return; + } + wl_event_source_fd_update(xwm->event_source, WL_EVENT_READABLE | WL_EVENT_WRITABLE); } From d362ed1eb9a6f3f91aff43df5b3a32c948c6a827 Mon Sep 17 00:00:00 2001 From: YaoBing Xiao Date: Wed, 4 Feb 2026 21:11:29 +0800 Subject: [PATCH 243/311] xwayland: fix wl_array rollback when adding selection targets Ensure mime_types and mime_types_atoms remain in sync when wl_array_add() fails. Roll back the partially added entry and free the allocated mime type to avoid leaks and inconsistent state. --- xwayland/selection/incoming.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/xwayland/selection/incoming.c b/xwayland/selection/incoming.c index 72f82c279..85d7775cd 100644 --- a/xwayland/selection/incoming.c +++ b/xwayland/selection/incoming.c @@ -381,13 +381,15 @@ static bool source_get_targets(struct wlr_xwm_selection *selection, free(mime_type); break; } - *mime_type_ptr = mime_type; xcb_atom_t *atom_ptr = wl_array_add(mime_types_atoms, sizeof(*atom_ptr)); if (atom_ptr == NULL) { + mime_types->size -= sizeof(*mime_type_ptr); + free(mime_type); break; } + *mime_type_ptr = mime_type; *atom_ptr = value[i]; } } From bc11ac92ab328cc45c29dd27f114ce4af756688e Mon Sep 17 00:00:00 2001 From: rewine Date: Fri, 6 Feb 2026 10:41:42 +0800 Subject: [PATCH 244/311] ext_image_capture_source_v1: remove unused variable --- types/ext_image_capture_source_v1/scene.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/types/ext_image_capture_source_v1/scene.c b/types/ext_image_capture_source_v1/scene.c index d3bf86b6c..99d34e012 100644 --- a/types/ext_image_capture_source_v1/scene.c +++ b/types/ext_image_capture_source_v1/scene.c @@ -173,8 +173,6 @@ static void source_update_buffer_constraints(struct scene_node_source *source, } static bool output_test(struct wlr_output *output, const struct wlr_output_state *state) { - struct scene_node_source *source = wl_container_of(output, source, output); - uint32_t supported = WLR_OUTPUT_STATE_BACKEND_OPTIONAL | WLR_OUTPUT_STATE_BUFFER | From dfccf5ff02eaea45d76edb40d2f92683de190800 Mon Sep 17 00:00:00 2001 From: YaoBing Xiao Date: Fri, 6 Feb 2026 10:18:21 +0800 Subject: [PATCH 245/311] output/cursor: fix missing newline at end of file --- types/output/cursor.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/output/cursor.c b/types/output/cursor.c index 11f442cdf..5e93b0b2e 100644 --- a/types/output/cursor.c +++ b/types/output/cursor.c @@ -535,4 +535,4 @@ bool output_cursor_refresh_color_transform(struct wlr_output_cursor *output_curs wlr_color_transform_unref(transforms[0]); wlr_color_transform_unref(transforms[1]); return output_cursor->color_transform != NULL; -} \ No newline at end of file +} From 19d682960158df3c7c2c28a54d180ededdb84977 Mon Sep 17 00:00:00 2001 From: Steve Williams Date: Sat, 31 Jan 2026 15:00:57 +0400 Subject: [PATCH 246/311] render/pixel-format: add function to determine YCbCr from drm fourcc --- include/render/pixel_format.h | 5 +++ render/pixel_format.c | 65 +++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/include/render/pixel_format.h b/include/render/pixel_format.h index e0b500c75..b5f2b2411 100644 --- a/include/render/pixel_format.h +++ b/include/render/pixel_format.h @@ -63,4 +63,9 @@ enum wl_shm_format convert_drm_format_to_wl_shm(uint32_t fmt); */ bool pixel_format_has_alpha(uint32_t fmt); +/** + * Return true if the DRM FourCC fmt belongs to a YCbCr colorspace family, false otherwise. + */ +bool pixel_format_is_ycbcr(uint32_t fmt); + #endif diff --git a/render/pixel_format.c b/render/pixel_format.c index 5f2e8644a..c60dd9d2a 100644 --- a/render/pixel_format.c +++ b/render/pixel_format.c @@ -307,3 +307,68 @@ bool pixel_format_has_alpha(uint32_t fmt) { } return true; } + +bool pixel_format_is_ycbcr(uint32_t format) { + switch (format) { + case DRM_FORMAT_AYUV: + case DRM_FORMAT_NV12: + case DRM_FORMAT_NV15: + case DRM_FORMAT_NV16: + case DRM_FORMAT_NV20: + case DRM_FORMAT_NV21: + case DRM_FORMAT_NV24: + case DRM_FORMAT_NV30: + case DRM_FORMAT_NV42: + case DRM_FORMAT_NV61: + case DRM_FORMAT_P010: + case DRM_FORMAT_P012: + case DRM_FORMAT_P016: + case DRM_FORMAT_P030: + case DRM_FORMAT_P210: + case DRM_FORMAT_Q401: + case DRM_FORMAT_Q410: + case DRM_FORMAT_S010: + case DRM_FORMAT_S012: + case DRM_FORMAT_S016: + case DRM_FORMAT_S210: + case DRM_FORMAT_S212: + case DRM_FORMAT_S216: + case DRM_FORMAT_S410: + case DRM_FORMAT_S412: + case DRM_FORMAT_S416: + case DRM_FORMAT_UYVY: + case DRM_FORMAT_VUY101010: + case DRM_FORMAT_VUY888: + case DRM_FORMAT_VYUY: + case DRM_FORMAT_X0L0: + case DRM_FORMAT_X0L2: + case DRM_FORMAT_XVYU12_16161616: + case DRM_FORMAT_XVYU16161616: + case DRM_FORMAT_XVYU2101010: + case DRM_FORMAT_XYUV8888: + case DRM_FORMAT_Y0L0: + case DRM_FORMAT_Y0L2: + case DRM_FORMAT_Y210: + case DRM_FORMAT_Y212: + case DRM_FORMAT_Y216: + case DRM_FORMAT_Y410: + case DRM_FORMAT_Y412: + case DRM_FORMAT_Y416: + case DRM_FORMAT_YUV410: + case DRM_FORMAT_YUV411: + case DRM_FORMAT_YUV420: + case DRM_FORMAT_YUV420_10BIT: + case DRM_FORMAT_YUV420_8BIT: + case DRM_FORMAT_YUV422: + case DRM_FORMAT_YUV444: + case DRM_FORMAT_YUYV: + case DRM_FORMAT_YVU410: + case DRM_FORMAT_YVU411: + case DRM_FORMAT_YVU420: + case DRM_FORMAT_YVU422: + case DRM_FORMAT_YVU444: + case DRM_FORMAT_YVYU: + return true; + } + return false; +} From 4c81cb1b9e94cc9be72831b8973c6b6b83e3746a Mon Sep 17 00:00:00 2001 From: Steve Williams Date: Sat, 31 Jan 2026 15:03:37 +0400 Subject: [PATCH 247/311] vulkan: make use of new pixel_format_is_ycbcr function --- include/render/vulkan.h | 2 +- render/vulkan/pass.c | 7 ++++--- render/vulkan/pixel_format.c | 24 ++++++++---------------- render/vulkan/renderer.c | 4 ++-- render/vulkan/texture.c | 10 +++++----- 5 files changed, 20 insertions(+), 27 deletions(-) diff --git a/include/render/vulkan.h b/include/render/vulkan.h index bb56b5534..2f8d9d241 100644 --- a/include/render/vulkan.h +++ b/include/render/vulkan.h @@ -86,7 +86,6 @@ struct wlr_vk_format { uint32_t drm; VkFormat vk; VkFormat vk_srgb; // sRGB version of the format, or 0 if nonexistent - bool is_ycbcr; }; extern const VkImageUsageFlags vulkan_render_usage, vulkan_shm_tex_usage, vulkan_dma_tex_usage; @@ -125,6 +124,7 @@ void vulkan_format_props_query(struct wlr_vk_device *dev, const struct wlr_vk_format_modifier_props *vulkan_format_props_find_modifier( const struct wlr_vk_format_props *props, uint64_t mod, bool render); void vulkan_format_props_finish(struct wlr_vk_format_props *props); +bool vulkan_format_is_ycbcr(const struct wlr_vk_format *format); struct wlr_vk_pipeline_layout_key { enum wlr_scale_filter_mode filter_mode; diff --git a/render/vulkan/pass.c b/render/vulkan/pass.c index 31f5116bd..271b426cc 100644 --- a/render/vulkan/pass.c +++ b/render/vulkan/pass.c @@ -822,12 +822,13 @@ static void render_pass_add_texture(struct wlr_render_pass *wlr_pass, } enum wlr_color_encoding color_encoding = options->color_encoding; - if (texture->format->is_ycbcr && color_encoding == WLR_COLOR_ENCODING_NONE) { + bool is_ycbcr = vulkan_format_is_ycbcr(texture->format); + if (is_ycbcr && color_encoding == WLR_COLOR_ENCODING_NONE) { color_encoding = WLR_COLOR_ENCODING_BT601; } enum wlr_color_range color_range = options->color_range; - if (texture->format->is_ycbcr && color_range == WLR_COLOR_RANGE_NONE) { + if (is_ycbcr && color_range == WLR_COLOR_RANGE_NONE) { color_range = WLR_COLOR_RANGE_LIMITED; } @@ -837,7 +838,7 @@ static void render_pass_add_texture(struct wlr_render_pass *wlr_pass, .source = WLR_VK_SHADER_SOURCE_TEXTURE, .layout = { .ycbcr = { - .format = texture->format->is_ycbcr ? texture->format : NULL, + .format = is_ycbcr ? texture->format : NULL, .encoding = color_encoding, .range = color_range, }, diff --git a/render/vulkan/pixel_format.c b/render/vulkan/pixel_format.c index 902feac9a..9b17d079a 100644 --- a/render/vulkan/pixel_format.c +++ b/render/vulkan/pixel_format.c @@ -182,37 +182,30 @@ static const struct wlr_vk_format formats[] = { { .drm = DRM_FORMAT_UYVY, .vk = VK_FORMAT_B8G8R8G8_422_UNORM, - .is_ycbcr = true, }, { .drm = DRM_FORMAT_YUYV, .vk = VK_FORMAT_G8B8G8R8_422_UNORM, - .is_ycbcr = true, }, { .drm = DRM_FORMAT_NV12, .vk = VK_FORMAT_G8_B8R8_2PLANE_420_UNORM, - .is_ycbcr = true, }, { .drm = DRM_FORMAT_NV16, .vk = VK_FORMAT_G8_B8R8_2PLANE_422_UNORM, - .is_ycbcr = true, }, { .drm = DRM_FORMAT_YUV420, .vk = VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM, - .is_ycbcr = true, }, { .drm = DRM_FORMAT_YUV422, .vk = VK_FORMAT_G8_B8_R8_3PLANE_422_UNORM, - .is_ycbcr = true, }, { .drm = DRM_FORMAT_YUV444, .vk = VK_FORMAT_G8_B8_R8_3PLANE_444_UNORM, - .is_ycbcr = true, }, // 3PACK16 formats split the memory in three 16-bit words, so they have an // inverted channel order compared to DRM formats. @@ -220,27 +213,22 @@ static const struct wlr_vk_format formats[] = { { .drm = DRM_FORMAT_P010, .vk = VK_FORMAT_G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16, - .is_ycbcr = true, }, { .drm = DRM_FORMAT_P210, .vk = VK_FORMAT_G10X6_B10X6R10X6_2PLANE_422_UNORM_3PACK16, - .is_ycbcr = true, }, { .drm = DRM_FORMAT_P012, .vk = VK_FORMAT_G12X4_B12X4R12X4_2PLANE_420_UNORM_3PACK16, - .is_ycbcr = true, }, { .drm = DRM_FORMAT_P016, .vk = VK_FORMAT_G16_B16R16_2PLANE_420_UNORM, - .is_ycbcr = true, }, { .drm = DRM_FORMAT_Q410, .vk = VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_444_UNORM_3PACK16, - .is_ycbcr = true, }, #endif // TODO: add DRM_FORMAT_NV24/VK_FORMAT_G8_B8R8_2PLANE_444_UNORM (requires @@ -446,7 +434,7 @@ static bool query_modifier_support(struct wlr_vk_device *dev, // check that specific modifier for render usage const char *errmsg = "unknown error"; if ((m.drmFormatModifierTilingFeatures & render_features) == render_features && - !props->format.is_ycbcr) { + !vulkan_format_is_ycbcr(&props->format)) { struct wlr_vk_format_modifier_props p = {0}; bool supported = false; if (query_modifier_usage_support(dev, props->format.vk, @@ -477,7 +465,7 @@ static bool query_modifier_support(struct wlr_vk_device *dev, // check that specific modifier for texture usage errmsg = "unknown error"; VkFormatFeatureFlags features = dma_tex_features; - if (props->format.is_ycbcr) { + if (vulkan_format_is_ycbcr(&props->format)) { features |= ycbcr_tex_features; } if ((m.drmFormatModifierTilingFeatures & features) == features) { @@ -522,7 +510,7 @@ static bool query_modifier_support(struct wlr_vk_device *dev, void vulkan_format_props_query(struct wlr_vk_device *dev, const struct wlr_vk_format *format) { - if (format->is_ycbcr && !dev->sampler_ycbcr_conversion) { + if (vulkan_format_is_ycbcr(format) && !dev->sampler_ycbcr_conversion) { return; } @@ -551,7 +539,7 @@ void vulkan_format_props_query(struct wlr_vk_device *dev, char shm_texture_status[256]; const char *errmsg = "unknown error"; if ((fmtp.formatProperties.optimalTilingFeatures & shm_tex_features) == shm_tex_features && - !format->is_ycbcr && format_info != NULL) { + !vulkan_format_is_ycbcr(format) && format_info != NULL) { VkImageFormatProperties ifmtp; bool supported = false, has_mutable_srgb = false; if (query_shm_support(dev, format->vk, format->vk_srgb, &ifmtp, &errmsg)) { @@ -621,3 +609,7 @@ const struct wlr_vk_format_modifier_props *vulkan_format_props_find_modifier( } return NULL; } + +bool vulkan_format_is_ycbcr(const struct wlr_vk_format *format) { + return pixel_format_is_ycbcr(format->drm); +} diff --git a/render/vulkan/renderer.c b/render/vulkan/renderer.c index 0b411f5dd..cfa05e077 100644 --- a/render/vulkan/renderer.c +++ b/render/vulkan/renderer.c @@ -1630,8 +1630,8 @@ static bool init_blend_to_output_layouts(struct wlr_vk_renderer *renderer) { static bool pipeline_layout_key_equals( const struct wlr_vk_pipeline_layout_key *a, const struct wlr_vk_pipeline_layout_key *b) { - assert(!a->ycbcr.format || a->ycbcr.format->is_ycbcr); - assert(!b->ycbcr.format || b->ycbcr.format->is_ycbcr); + assert(!a->ycbcr.format || vulkan_format_is_ycbcr(a->ycbcr.format)); + assert(!b->ycbcr.format || vulkan_format_is_ycbcr(b->ycbcr.format)); if (a->filter_mode != b->filter_mode) { return false; diff --git a/render/vulkan/texture.c b/render/vulkan/texture.c index 499178f5d..ffea87795 100644 --- a/render/vulkan/texture.c +++ b/render/vulkan/texture.c @@ -297,7 +297,7 @@ struct wlr_vk_texture_view *vulkan_texture_get_or_create_view(struct wlr_vk_text .components.r = VK_COMPONENT_SWIZZLE_IDENTITY, .components.g = VK_COMPONENT_SWIZZLE_IDENTITY, .components.b = VK_COMPONENT_SWIZZLE_IDENTITY, - .components.a = texture->has_alpha || texture->format->is_ycbcr + .components.a = texture->has_alpha || vulkan_format_is_ycbcr(texture->format) ? VK_COMPONENT_SWIZZLE_IDENTITY : VK_COMPONENT_SWIZZLE_ONE, .subresourceRange = (VkImageSubresourceRange){ @@ -311,7 +311,7 @@ struct wlr_vk_texture_view *vulkan_texture_get_or_create_view(struct wlr_vk_text }; VkSamplerYcbcrConversionInfo ycbcr_conversion_info; - if (texture->format->is_ycbcr) { + if (vulkan_format_is_ycbcr(texture->format)) { assert(pipeline_layout->ycbcr.conversion != VK_NULL_HANDLE); ycbcr_conversion_info = (VkSamplerYcbcrConversionInfo){ .sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO, @@ -355,7 +355,7 @@ struct wlr_vk_texture_view *vulkan_texture_get_or_create_view(struct wlr_vk_text static void texture_set_format(struct wlr_vk_texture *texture, const struct wlr_vk_format *format, bool has_mutable_srgb) { - assert(!(format->is_ycbcr && has_mutable_srgb)); + assert(!(vulkan_format_is_ycbcr(format) && has_mutable_srgb)); texture->format = format; texture->using_mutable_srgb = has_mutable_srgb; @@ -366,7 +366,7 @@ static void texture_set_format(struct wlr_vk_texture *texture, texture->has_alpha = pixel_format_has_alpha(format->drm); } else { // We don't have format info for multi-planar formats - assert(texture->format->is_ycbcr); + assert(vulkan_format_is_ycbcr(texture->format)); } } @@ -378,7 +378,7 @@ static struct wlr_texture *vulkan_texture_from_pixels( const struct wlr_vk_format_props *fmt = vulkan_format_props_from_drm(renderer->dev, drm_fmt); - if (fmt == NULL || fmt->format.is_ycbcr) { + if (fmt == NULL || vulkan_format_is_ycbcr(&fmt->format)) { char *format_name = drmGetFormatName(drm_fmt); wlr_log(WLR_ERROR, "Unsupported pixel format %s (0x%08"PRIX32")", format_name, drm_fmt); From 439985fe95efec4ccb06f5e690b33422a40dc512 Mon Sep 17 00:00:00 2001 From: Steve Williams Date: Sat, 31 Jan 2026 15:10:49 +0400 Subject: [PATCH 248/311] color_representation: ensure encoding/range/drm formats compatibility --- types/wlr_color_representation_v1.c | 38 +++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/types/wlr_color_representation_v1.c b/types/wlr_color_representation_v1.c index a447d77a7..a69ea46e5 100644 --- a/types/wlr_color_representation_v1.c +++ b/types/wlr_color_representation_v1.c @@ -1,13 +1,16 @@ #include +#include #include #include +#include #include #include #include #include #include "color-representation-v1-protocol.h" +#include "render/pixel_format.h" #include "util/mem.h" #define WP_COLOR_REPRESENTATION_VERSION 1 @@ -230,8 +233,42 @@ static void color_repr_manager_handle_destroy(struct wl_client *client, wl_resource_destroy(resource); } +static void surface_synced_commit(struct wlr_surface_synced *synced) { + struct wlr_color_representation_v1 *color_repr = wl_container_of(synced, color_repr, synced); + + if (color_repr->current.coefficients == 0 && color_repr->current.range == 0) { + return; + } + + struct wlr_dmabuf_attributes dmabuf; + + if (!color_repr->surface->buffer || + !wlr_buffer_get_dmabuf(&color_repr->surface->buffer->base, &dmabuf)) { + return; + } + + bool is_ycbcr = pixel_format_is_ycbcr(dmabuf.format); + + bool is_identity_full = + color_repr->current.coefficients == WP_COLOR_REPRESENTATION_SURFACE_V1_COEFFICIENTS_IDENTITY && + color_repr->current.range == WP_COLOR_REPRESENTATION_SURFACE_V1_RANGE_FULL; + + if (is_ycbcr) { + if (is_identity_full) { + wl_resource_post_error(color_repr->resource, + WP_COLOR_REPRESENTATION_SURFACE_V1_ERROR_PIXEL_FORMAT, + "unexpected encoding/range for yuv"); + } + } else /* rgb */ { + wl_resource_post_error(color_repr->resource, + WP_COLOR_REPRESENTATION_SURFACE_V1_ERROR_PIXEL_FORMAT, + "unexpected encoding/range for rgb"); + } +} + static const struct wlr_surface_synced_impl surface_synced_impl = { .state_size = sizeof(struct wlr_color_representation_v1_surface_state), + .commit = surface_synced_commit }; static struct wlr_color_representation_v1 *color_repr_from_surface( @@ -276,6 +313,7 @@ static void color_repr_manager_handle_get_surface(struct wl_client *client, } color_repr->manager = manager_from_resource(manager_resource); + color_repr->surface = surface; if (!wlr_surface_synced_init(&color_repr->synced, surface, &surface_synced_impl, &color_repr->pending, &color_repr->current)) { From bb78861ca90e2dbac5e8823e1706e44cdf34b37b Mon Sep 17 00:00:00 2001 From: Steve Williams Date: Sat, 31 Jan 2026 15:11:14 +0400 Subject: [PATCH 249/311] color-representation: add support for identity+full --- render/vulkan/renderer.c | 4 ++-- types/scene/wlr_scene.c | 8 ++++++-- types/wlr_color_representation_v1.c | 8 +++++++- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/render/vulkan/renderer.c b/render/vulkan/renderer.c index cfa05e077..7790d40b2 100644 --- a/render/vulkan/renderer.c +++ b/render/vulkan/renderer.c @@ -2039,8 +2039,8 @@ struct wlr_vk_pipeline_layout *get_or_create_pipeline_layout( }; sampler_create_info.pNext = &conversion_info; } else { - assert(key->ycbcr.encoding == WLR_COLOR_ENCODING_NONE); - assert(key->ycbcr.range == WLR_COLOR_RANGE_NONE); + assert(key->ycbcr.encoding == WLR_COLOR_ENCODING_NONE || key->ycbcr.encoding == WLR_COLOR_ENCODING_IDENTITY); + assert(key->ycbcr.range == WLR_COLOR_RANGE_NONE || key->ycbcr.range == WLR_COLOR_RANGE_FULL); } res = vkCreateSampler(renderer->dev->dev, &sampler_create_info, NULL, &pipeline_layout->sampler); diff --git a/types/scene/wlr_scene.c b/types/scene/wlr_scene.c index 19617b11f..aaf9a8d74 100644 --- a/types/scene/wlr_scene.c +++ b/types/scene/wlr_scene.c @@ -2086,8 +2086,12 @@ static enum scene_direct_scanout_result scene_entry_try_direct_scanout( return SCANOUT_INELIGIBLE; } - if (buffer->color_encoding != WLR_COLOR_ENCODING_NONE || - buffer->color_range != WLR_COLOR_RANGE_NONE) { + bool is_color_repr_none = buffer->color_encoding == WLR_COLOR_ENCODING_NONE && + buffer->color_range == WLR_COLOR_RANGE_NONE; + bool is_color_repr_identity_full = buffer->color_encoding == WLR_COLOR_ENCODING_IDENTITY && + buffer->color_range == WLR_COLOR_RANGE_FULL; + + if (!(is_color_repr_none || is_color_repr_identity_full)) { return SCANOUT_INELIGIBLE; } diff --git a/types/wlr_color_representation_v1.c b/types/wlr_color_representation_v1.c index a69ea46e5..432511b91 100644 --- a/types/wlr_color_representation_v1.c +++ b/types/wlr_color_representation_v1.c @@ -260,9 +260,11 @@ static void surface_synced_commit(struct wlr_surface_synced *synced) { "unexpected encoding/range for yuv"); } } else /* rgb */ { - wl_resource_post_error(color_repr->resource, + if (!is_identity_full) { + wl_resource_post_error(color_repr->resource, WP_COLOR_REPRESENTATION_SURFACE_V1_ERROR_PIXEL_FORMAT, "unexpected encoding/range for rgb"); + } } } @@ -465,6 +467,10 @@ struct wlr_color_representation_manager_v1 *wlr_color_representation_manager_v1_ struct wlr_color_representation_v1_coeffs_and_range coeffs_and_ranges[COEFFICIENTS_LEN * RANGES_LEN]; size_t coeffs_and_ranges_len = 0; + coeffs_and_ranges[coeffs_and_ranges_len++] = (struct wlr_color_representation_v1_coeffs_and_range){ + .coeffs = WP_COLOR_REPRESENTATION_SURFACE_V1_COEFFICIENTS_IDENTITY, + .range = WP_COLOR_REPRESENTATION_SURFACE_V1_RANGE_FULL, + }; for (size_t i = 0; i < COEFFICIENTS_LEN; i++) { enum wp_color_representation_surface_v1_coefficients coeffs = coefficients[i]; enum wlr_color_encoding enc = wlr_color_representation_v1_color_encoding_to_wlr(coeffs); From ef882466421492061a776c2025513a03598faeb5 Mon Sep 17 00:00:00 2001 From: Steve Williams Date: Thu, 5 Feb 2026 21:26:29 +0400 Subject: [PATCH 250/311] types/wlr_buffer: add buffer_get_drm_format helper function --- backend/x11/output.c | 12 +++--------- include/types/wlr_buffer.h | 6 ++++++ types/buffer/buffer.c | 23 ++++++++++++++++------- types/wlr_color_representation_v1.c | 13 +++++++------ 4 files changed, 32 insertions(+), 22 deletions(-) diff --git a/backend/x11/output.c b/backend/x11/output.c index 0b63a7088..181c2de14 100644 --- a/backend/x11/output.c +++ b/backend/x11/output.c @@ -20,6 +20,7 @@ #include "backend/x11.h" #include "util/time.h" +#include "types/wlr_buffer.h" #include "types/wlr_output.h" static const uint32_t SUPPORTED_OUTPUT_STATE = @@ -166,15 +167,8 @@ static bool output_test(struct wlr_output *wlr_output, if (state->committed & WLR_OUTPUT_STATE_BUFFER) { struct wlr_buffer *buffer = state->buffer; - struct wlr_dmabuf_attributes dmabuf_attrs; - struct wlr_shm_attributes shm_attrs; - uint32_t format = DRM_FORMAT_INVALID; - if (wlr_buffer_get_dmabuf(buffer, &dmabuf_attrs)) { - format = dmabuf_attrs.format; - } else if (wlr_buffer_get_shm(buffer, &shm_attrs)) { - format = shm_attrs.format; - } - if (format != x11->x11_format->drm) { + + if (buffer_get_drm_format(buffer) != x11->x11_format->drm) { wlr_log(WLR_DEBUG, "Unsupported buffer format"); return false; } diff --git a/include/types/wlr_buffer.h b/include/types/wlr_buffer.h index 9d882d47d..45acf7a1d 100644 --- a/include/types/wlr_buffer.h +++ b/include/types/wlr_buffer.h @@ -65,4 +65,10 @@ struct wlr_client_buffer *wlr_client_buffer_create(struct wlr_buffer *buffer, bool wlr_client_buffer_apply_damage(struct wlr_client_buffer *client_buffer, struct wlr_buffer *next, const pixman_region32_t *damage); +/** + * Return the DRM format of the buffer. If this buffer isn't shared + * memory or a DMA-BUF, returns DRM_FORMAT_INVALID. + */ +uint32_t buffer_get_drm_format(struct wlr_buffer *buffer); + #endif diff --git a/types/buffer/buffer.c b/types/buffer/buffer.c index d56255b0d..48a10d9f0 100644 --- a/types/buffer/buffer.c +++ b/types/buffer/buffer.c @@ -109,14 +109,11 @@ bool wlr_buffer_get_shm(struct wlr_buffer *buffer, bool wlr_buffer_is_opaque(struct wlr_buffer *buffer) { void *data; - uint32_t format; + uint32_t format = buffer_get_drm_format(buffer); size_t stride; - struct wlr_dmabuf_attributes dmabuf; - struct wlr_shm_attributes shm; - if (wlr_buffer_get_dmabuf(buffer, &dmabuf)) { - format = dmabuf.format; - } else if (wlr_buffer_get_shm(buffer, &shm)) { - format = shm.format; + + if (format != DRM_FORMAT_INVALID) { + // pass } else if (wlr_buffer_begin_data_ptr_access(buffer, WLR_BUFFER_DATA_PTR_ACCESS_READ, &data, &format, &stride)) { bool opaque = false; @@ -135,3 +132,15 @@ bool wlr_buffer_is_opaque(struct wlr_buffer *buffer) { return !pixel_format_has_alpha(format); } + +uint32_t buffer_get_drm_format(struct wlr_buffer *buffer) { + uint32_t format = DRM_FORMAT_INVALID; + struct wlr_dmabuf_attributes dmabuf; + struct wlr_shm_attributes shm; + if (wlr_buffer_get_dmabuf(buffer, &dmabuf)) { + format = dmabuf.format; + } else if (wlr_buffer_get_shm(buffer, &shm)) { + format = shm.format; + } + return format; +} diff --git a/types/wlr_color_representation_v1.c b/types/wlr_color_representation_v1.c index 432511b91..ab7df30b0 100644 --- a/types/wlr_color_representation_v1.c +++ b/types/wlr_color_representation_v1.c @@ -11,6 +11,7 @@ #include "color-representation-v1-protocol.h" #include "render/pixel_format.h" +#include "types/wlr_buffer.h" #include "util/mem.h" #define WP_COLOR_REPRESENTATION_VERSION 1 @@ -240,14 +241,14 @@ static void surface_synced_commit(struct wlr_surface_synced *synced) { return; } - struct wlr_dmabuf_attributes dmabuf; - - if (!color_repr->surface->buffer || - !wlr_buffer_get_dmabuf(&color_repr->surface->buffer->base, &dmabuf)) { + uint32_t drm_format = DRM_FORMAT_INVALID; + if (!color_repr->surface->buffer){ + drm_format = buffer_get_drm_format(&color_repr->surface->buffer->base); + } + if (drm_format == DRM_FORMAT_INVALID) { return; } - - bool is_ycbcr = pixel_format_is_ycbcr(dmabuf.format); + bool is_ycbcr = pixel_format_is_ycbcr(drm_format); bool is_identity_full = color_repr->current.coefficients == WP_COLOR_REPRESENTATION_SURFACE_V1_COEFFICIENTS_IDENTITY && From 436bcf9a4755b24f7fadc100ba8deaa7f343ba86 Mon Sep 17 00:00:00 2001 From: Steve Williams Date: Sat, 7 Feb 2026 08:36:29 +0400 Subject: [PATCH 251/311] color-representation-v1: fix condition in surface commit --- types/wlr_color_representation_v1.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/wlr_color_representation_v1.c b/types/wlr_color_representation_v1.c index ab7df30b0..856dc84f8 100644 --- a/types/wlr_color_representation_v1.c +++ b/types/wlr_color_representation_v1.c @@ -242,7 +242,7 @@ static void surface_synced_commit(struct wlr_surface_synced *synced) { } uint32_t drm_format = DRM_FORMAT_INVALID; - if (!color_repr->surface->buffer){ + if (color_repr->surface->buffer){ drm_format = buffer_get_drm_format(&color_repr->surface->buffer->base); } if (drm_format == DRM_FORMAT_INVALID) { From c1452d88114710f5772662b1d8efb9c71edaa34c Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Sat, 7 Feb 2026 17:48:16 -0800 Subject: [PATCH 252/311] backend/libinput: fix build with libinput 1.31 --- backend/libinput/meson.build | 4 ++++ backend/libinput/switch.c | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/backend/libinput/meson.build b/backend/libinput/meson.build index c244eb77a..091b0e0eb 100644 --- a/backend/libinput/meson.build +++ b/backend/libinput/meson.build @@ -29,3 +29,7 @@ features += { 'libinput-backend': true } wlr_deps += libinput internal_config.set10('HAVE_LIBINPUT_BUSTYPE', libinput.version().version_compare('>=1.26.0')) +internal_config.set10( + 'HAVE_LIBINPUT_SWITCH_KEYPAD_SLIDE', + libinput.version().version_compare('>=1.30.901') +) diff --git a/backend/libinput/switch.c b/backend/libinput/switch.c index abeec86d7..9dde3c9cb 100644 --- a/backend/libinput/switch.c +++ b/backend/libinput/switch.c @@ -2,6 +2,7 @@ #include #include #include "backend/libinput.h" +#include "config.h" const struct wlr_switch_impl libinput_switch_impl = { .name = "libinput-switch", @@ -36,6 +37,10 @@ void handle_switch_toggle(struct libinput_event *event, case LIBINPUT_SWITCH_TABLET_MODE: wlr_event.switch_type = WLR_SWITCH_TYPE_TABLET_MODE; break; +#if HAVE_LIBINPUT_SWITCH_KEYPAD_SLIDE + case LIBINPUT_SWITCH_KEYPAD_SLIDE: + return; +#endif } switch (libinput_event_switch_get_switch_state(sevent)) { case LIBINPUT_SWITCH_STATE_OFF: From 3676ab4df0e7b31efd9c72f543fd8e6326af95cc Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Sat, 7 Feb 2026 17:48:16 -0800 Subject: [PATCH 253/311] backend/libinput: add support for LIBINPUT_SWITCH_KEYPAD_SLIDE --- backend/libinput/switch.c | 3 ++- include/wlr/types/wlr_switch.h | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/backend/libinput/switch.c b/backend/libinput/switch.c index 9dde3c9cb..a78d37abe 100644 --- a/backend/libinput/switch.c +++ b/backend/libinput/switch.c @@ -39,7 +39,8 @@ void handle_switch_toggle(struct libinput_event *event, break; #if HAVE_LIBINPUT_SWITCH_KEYPAD_SLIDE case LIBINPUT_SWITCH_KEYPAD_SLIDE: - return; + wlr_event.switch_type = WLR_SWITCH_TYPE_KEYPAD_SLIDE; + break; #endif } switch (libinput_event_switch_get_switch_state(sevent)) { diff --git a/include/wlr/types/wlr_switch.h b/include/wlr/types/wlr_switch.h index 641df1991..2b9e91266 100644 --- a/include/wlr/types/wlr_switch.h +++ b/include/wlr/types/wlr_switch.h @@ -36,6 +36,7 @@ struct wlr_switch { enum wlr_switch_type { WLR_SWITCH_TYPE_LID, WLR_SWITCH_TYPE_TABLET_MODE, + WLR_SWITCH_TYPE_KEYPAD_SLIDE, }; enum wlr_switch_state { From 910fd264fb78f1ee6d68b79c883285c57419c7f3 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Thu, 12 Feb 2026 22:47:22 +0100 Subject: [PATCH 254/311] build: bump version to 0.20.0-rc2 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 5b8f12073..b5b756bae 100644 --- a/meson.build +++ b/meson.build @@ -1,7 +1,7 @@ project( 'wlroots', 'c', - version: '0.20.0-rc1', + version: '0.20.0-rc2', license: 'MIT', meson_version: '>=1.3', default_options: [ From 25f94c59657e45f087f3429c0fa979eae5a136e0 Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Fri, 13 Feb 2026 11:48:17 +0100 Subject: [PATCH 255/311] backend/x11: reject shm buffers with non-min strides This fixes an X11 backend direct scanout bug with foot. Closes: https://gitlab.freedesktop.org/wlroots/wlroots/-/issues/4046 --- backend/x11/output.c | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/backend/x11/output.c b/backend/x11/output.c index 181c2de14..51a9c1441 100644 --- a/backend/x11/output.c +++ b/backend/x11/output.c @@ -19,6 +19,7 @@ #include #include "backend/x11.h" +#include "render/pixel_format.h" #include "util/time.h" #include "types/wlr_buffer.h" #include "types/wlr_output.h" @@ -167,11 +168,20 @@ static bool output_test(struct wlr_output *wlr_output, if (state->committed & WLR_OUTPUT_STATE_BUFFER) { struct wlr_buffer *buffer = state->buffer; - - if (buffer_get_drm_format(buffer) != x11->x11_format->drm) { + uint32_t format = buffer_get_drm_format(buffer); + if (format != x11->x11_format->drm) { wlr_log(WLR_DEBUG, "Unsupported buffer format"); return false; } + struct wlr_shm_attributes shm; + if (wlr_buffer_get_shm(buffer, &shm)) { + const struct wlr_pixel_format_info *info = drm_get_pixel_format_info(format); + if (shm.stride != pixel_format_info_min_stride(info, shm.width)) { + // xcb_shm_create_pixmap() does not allow arbitrary strides. + wlr_log(WLR_DEBUG, "Unsupported shm buffer stride"); + return false; + } + } } if (state->committed & WLR_OUTPUT_STATE_MODE) { @@ -261,6 +271,12 @@ static xcb_pixmap_t import_shm(struct wlr_x11_output *output, return XCB_PIXMAP_NONE; } + const struct wlr_pixel_format_info *info = drm_get_pixel_format_info(shm->format); + if (shm->stride != pixel_format_info_min_stride(info, shm->width)) { + // xcb_shm_create_pixmap() does not allow arbitrary strides. + return XCB_PIXMAP_NONE; + } + // xcb closes the FD after sending it int fd = fcntl(shm->fd, F_DUPFD_CLOEXEC, 0); if (fd < 0) { From 884d29e5f31b5dec41fdbaa7876458e1b88a28be Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Thu, 12 Feb 2026 22:37:41 +0100 Subject: [PATCH 256/311] backend/libinput: guard against new enum entries When libinput introduces new enum entries, we'd abort or send bogus events to the compositor. Instead, log a message and ignore the event. Keep all enums without a default case so that the compiler warns when we're missing a case. --- backend/libinput/events.c | 12 ++++ backend/libinput/keyboard.c | 23 +++++--- backend/libinput/pointer.c | 60 ++++++++++++------- backend/libinput/switch.c | 57 +++++++++++------- backend/libinput/tablet_pad.c | 12 ++-- backend/libinput/tablet_tool.c | 102 ++++++++++++++++++++++----------- include/backend/libinput.h | 2 + 7 files changed, 179 insertions(+), 89 deletions(-) diff --git a/backend/libinput/events.c b/backend/libinput/events.c index 6cfe34e08..9be5def87 100644 --- a/backend/libinput/events.c +++ b/backend/libinput/events.c @@ -249,3 +249,15 @@ void handle_libinput_event(struct wlr_libinput_backend *backend, break; } } + +bool button_state_from_libinput(enum libinput_button_state state, enum wlr_button_state *out) { + switch (state) { + case LIBINPUT_BUTTON_STATE_RELEASED: + *out = WLR_BUTTON_RELEASED; + return true; + case LIBINPUT_BUTTON_STATE_PRESSED: + *out = WLR_BUTTON_PRESSED; + return true; + } + return false; +} diff --git a/backend/libinput/keyboard.c b/backend/libinput/keyboard.c index 7518453e6..a99461040 100644 --- a/backend/libinput/keyboard.c +++ b/backend/libinput/keyboard.c @@ -2,6 +2,7 @@ #include #include #include +#include #include "backend/libinput.h" struct wlr_libinput_input_device *device_from_keyboard( @@ -30,6 +31,18 @@ void init_device_keyboard(struct wlr_libinput_input_device *dev) { libinput_device_led_update(dev->handle, 0); } +static bool key_state_from_libinput(enum libinput_key_state state, enum wl_keyboard_key_state *out) { + switch (state) { + case LIBINPUT_KEY_STATE_RELEASED: + *out = WL_KEYBOARD_KEY_STATE_RELEASED; + return true; + case LIBINPUT_KEY_STATE_PRESSED: + *out = WL_KEYBOARD_KEY_STATE_PRESSED; + return true; + } + return false; +} + void handle_keyboard_key(struct libinput_event *event, struct wlr_keyboard *kb) { struct libinput_event_keyboard *kbevent = @@ -39,13 +52,9 @@ void handle_keyboard_key(struct libinput_event *event, .keycode = libinput_event_keyboard_get_key(kbevent), .update_state = true, }; - switch (libinput_event_keyboard_get_key_state(kbevent)) { - case LIBINPUT_KEY_STATE_RELEASED: - wlr_event.state = WL_KEYBOARD_KEY_STATE_RELEASED; - break; - case LIBINPUT_KEY_STATE_PRESSED: - wlr_event.state = WL_KEYBOARD_KEY_STATE_PRESSED; - break; + if (!key_state_from_libinput(libinput_event_keyboard_get_key_state(kbevent), &wlr_event.state)) { + wlr_log(WLR_DEBUG, "Unhandled libinput key state"); + return; } wlr_keyboard_notify_key(kb, &wlr_event); } diff --git a/backend/libinput/pointer.c b/backend/libinput/pointer.c index 9b9996780..d09164a71 100644 --- a/backend/libinput/pointer.c +++ b/backend/libinput/pointer.c @@ -1,6 +1,7 @@ #include #include #include +#include #include "backend/libinput.h" const struct wlr_pointer_impl libinput_pointer_impl = { @@ -52,6 +53,38 @@ void handle_pointer_motion_abs(struct libinput_event *event, wl_signal_emit_mutable(&pointer->events.frame, pointer); } +static bool pointer_button_state_from_libinput(enum libinput_button_state state, + enum wl_pointer_button_state *out) { + switch (state) { + case LIBINPUT_BUTTON_STATE_PRESSED: + *out = WL_POINTER_BUTTON_STATE_PRESSED; + return true; + case LIBINPUT_BUTTON_STATE_RELEASED: + *out = WL_POINTER_BUTTON_STATE_RELEASED; + return true; + } + return false; +} + +static bool axis_source_from_libinput(enum libinput_pointer_axis_source source, + enum wl_pointer_axis_source *out) { + switch (source) { + case LIBINPUT_POINTER_AXIS_SOURCE_WHEEL: + *out = WL_POINTER_AXIS_SOURCE_WHEEL; + return true; + case LIBINPUT_POINTER_AXIS_SOURCE_FINGER: + *out = WL_POINTER_AXIS_SOURCE_FINGER; + return true; + case LIBINPUT_POINTER_AXIS_SOURCE_CONTINUOUS: + *out = WL_POINTER_AXIS_SOURCE_CONTINUOUS; + return true; + case LIBINPUT_POINTER_AXIS_SOURCE_WHEEL_TILT: + *out = WL_POINTER_AXIS_SOURCE_WHEEL_TILT; + return true; + } + return false; +} + void handle_pointer_button(struct libinput_event *event, struct wlr_pointer *pointer) { struct libinput_event_pointer *pevent = @@ -61,13 +94,10 @@ void handle_pointer_button(struct libinput_event *event, .time_msec = usec_to_msec(libinput_event_pointer_get_time_usec(pevent)), .button = libinput_event_pointer_get_button(pevent), }; - switch (libinput_event_pointer_get_button_state(pevent)) { - case LIBINPUT_BUTTON_STATE_PRESSED: - wlr_event.state = WL_POINTER_BUTTON_STATE_PRESSED; - break; - case LIBINPUT_BUTTON_STATE_RELEASED: - wlr_event.state = WL_POINTER_BUTTON_STATE_RELEASED; - break; + if (!pointer_button_state_from_libinput(libinput_event_pointer_get_button_state(pevent), + &wlr_event.state)) { + wlr_log(WLR_DEBUG, "Unhandled libinput button state"); + return; } wlr_pointer_notify_button(pointer, &wlr_event); wl_signal_emit_mutable(&pointer->events.frame, pointer); @@ -81,19 +111,9 @@ void handle_pointer_axis(struct libinput_event *event, .pointer = pointer, .time_msec = usec_to_msec(libinput_event_pointer_get_time_usec(pevent)), }; - switch (libinput_event_pointer_get_axis_source(pevent)) { - case LIBINPUT_POINTER_AXIS_SOURCE_WHEEL: - wlr_event.source = WL_POINTER_AXIS_SOURCE_WHEEL; - break; - case LIBINPUT_POINTER_AXIS_SOURCE_FINGER: - wlr_event.source = WL_POINTER_AXIS_SOURCE_FINGER; - break; - case LIBINPUT_POINTER_AXIS_SOURCE_CONTINUOUS: - wlr_event.source = WL_POINTER_AXIS_SOURCE_CONTINUOUS; - break; - case LIBINPUT_POINTER_AXIS_SOURCE_WHEEL_TILT: - wlr_event.source = WL_POINTER_AXIS_SOURCE_WHEEL_TILT; - break; + if (!axis_source_from_libinput(libinput_event_pointer_get_axis_source(pevent), &wlr_event.source)) { + wlr_log(WLR_DEBUG, "Unhandled libinput pointer axis source"); + return; } const enum libinput_pointer_axis axes[] = { LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL, diff --git a/backend/libinput/switch.c b/backend/libinput/switch.c index a78d37abe..2055d88f5 100644 --- a/backend/libinput/switch.c +++ b/backend/libinput/switch.c @@ -1,6 +1,7 @@ #include #include #include +#include #include "backend/libinput.h" #include "config.h" @@ -23,33 +24,49 @@ struct wlr_libinput_input_device *device_from_switch( return dev; } +static bool switch_type_from_libinput(enum libinput_switch type, enum wlr_switch_type *out) { + switch (type) { + case LIBINPUT_SWITCH_LID: + *out = WLR_SWITCH_TYPE_LID; + return true; + case LIBINPUT_SWITCH_TABLET_MODE: + *out = WLR_SWITCH_TYPE_TABLET_MODE; + return true; +#if HAVE_LIBINPUT_SWITCH_KEYPAD_SLIDE + case LIBINPUT_SWITCH_KEYPAD_SLIDE: + *out = WLR_SWITCH_TYPE_KEYPAD_SLIDE; + return true; +#endif + } + return false; +} + +static bool switch_state_from_libinput(enum libinput_switch_state state, enum wlr_switch_state *out) { + switch (state) { + case LIBINPUT_SWITCH_STATE_OFF: + *out = WLR_SWITCH_STATE_OFF; + return true; + case LIBINPUT_SWITCH_STATE_ON: + *out = WLR_SWITCH_STATE_ON; + return true; + } + return false; +} + void handle_switch_toggle(struct libinput_event *event, struct wlr_switch *wlr_switch) { struct libinput_event_switch *sevent = - libinput_event_get_switch_event (event); + libinput_event_get_switch_event(event); struct wlr_switch_toggle_event wlr_event = { .time_msec = usec_to_msec(libinput_event_switch_get_time_usec(sevent)), }; - switch (libinput_event_switch_get_switch(sevent)) { - case LIBINPUT_SWITCH_LID: - wlr_event.switch_type = WLR_SWITCH_TYPE_LID; - break; - case LIBINPUT_SWITCH_TABLET_MODE: - wlr_event.switch_type = WLR_SWITCH_TYPE_TABLET_MODE; - break; -#if HAVE_LIBINPUT_SWITCH_KEYPAD_SLIDE - case LIBINPUT_SWITCH_KEYPAD_SLIDE: - wlr_event.switch_type = WLR_SWITCH_TYPE_KEYPAD_SLIDE; - break; -#endif + if (!switch_type_from_libinput(libinput_event_switch_get_switch(sevent), &wlr_event.switch_type)) { + wlr_log(WLR_DEBUG, "Unhandled libinput switch type"); + return; } - switch (libinput_event_switch_get_switch_state(sevent)) { - case LIBINPUT_SWITCH_STATE_OFF: - wlr_event.switch_state = WLR_SWITCH_STATE_OFF; - break; - case LIBINPUT_SWITCH_STATE_ON: - wlr_event.switch_state = WLR_SWITCH_STATE_ON; - break; + if (!switch_state_from_libinput(libinput_event_switch_get_switch_state(sevent), &wlr_event.switch_state)) { + wlr_log(WLR_DEBUG, "Unhandled libinput switch state"); + return; } wl_signal_emit_mutable(&wlr_switch->events.toggle, &wlr_event); } diff --git a/backend/libinput/tablet_pad.c b/backend/libinput/tablet_pad.c index 2fbfb6a6c..9d090198a 100644 --- a/backend/libinput/tablet_pad.c +++ b/backend/libinput/tablet_pad.c @@ -148,13 +148,9 @@ void handle_tablet_pad_button(struct libinput_event *event, .group = libinput_tablet_pad_mode_group_get_index( libinput_event_tablet_pad_get_mode_group(pevent)), }; - switch (libinput_event_tablet_pad_get_button_state(pevent)) { - case LIBINPUT_BUTTON_STATE_PRESSED: - wlr_event.state = WLR_BUTTON_PRESSED; - break; - case LIBINPUT_BUTTON_STATE_RELEASED: - wlr_event.state = WLR_BUTTON_RELEASED; - break; + if (!button_state_from_libinput(libinput_event_tablet_pad_get_button_state(pevent), &wlr_event.state)) { + wlr_log(WLR_DEBUG, "Unhandled libinput button state"); + return; } wl_signal_emit_mutable(&tablet_pad->events.button, &wlr_event); } @@ -168,6 +164,7 @@ void handle_tablet_pad_ring(struct libinput_event *event, .ring = libinput_event_tablet_pad_get_ring_number(pevent), .position = libinput_event_tablet_pad_get_ring_position(pevent), .mode = libinput_event_tablet_pad_get_mode(pevent), + .source = WLR_TABLET_PAD_RING_SOURCE_UNKNOWN, }; switch (libinput_event_tablet_pad_get_ring_source(pevent)) { case LIBINPUT_TABLET_PAD_RING_SOURCE_UNKNOWN: @@ -189,6 +186,7 @@ void handle_tablet_pad_strip(struct libinput_event *event, .strip = libinput_event_tablet_pad_get_strip_number(pevent), .position = libinput_event_tablet_pad_get_strip_position(pevent), .mode = libinput_event_tablet_pad_get_mode(pevent), + .source = WLR_TABLET_PAD_STRIP_SOURCE_UNKNOWN, }; switch (libinput_event_tablet_pad_get_strip_source(pevent)) { case LIBINPUT_TABLET_PAD_STRIP_SOURCE_UNKNOWN: diff --git a/backend/libinput/tablet_tool.c b/backend/libinput/tablet_tool.c index b2430a445..782e56a86 100644 --- a/backend/libinput/tablet_tool.c +++ b/backend/libinput/tablet_tool.c @@ -78,27 +78,61 @@ struct wlr_libinput_input_device *device_from_tablet( return dev; } -static enum wlr_tablet_tool_type wlr_type_from_libinput_type( - enum libinput_tablet_tool_type value) { - switch (value) { +static bool type_from_libinput(enum libinput_tablet_tool_type type, + enum wlr_tablet_tool_type *out) { + switch (type) { case LIBINPUT_TABLET_TOOL_TYPE_PEN: - return WLR_TABLET_TOOL_TYPE_PEN; + *out = WLR_TABLET_TOOL_TYPE_PEN; + return true; case LIBINPUT_TABLET_TOOL_TYPE_ERASER: - return WLR_TABLET_TOOL_TYPE_ERASER; + *out = WLR_TABLET_TOOL_TYPE_ERASER; + return true; case LIBINPUT_TABLET_TOOL_TYPE_BRUSH: - return WLR_TABLET_TOOL_TYPE_BRUSH; + *out = WLR_TABLET_TOOL_TYPE_BRUSH; + return true; case LIBINPUT_TABLET_TOOL_TYPE_PENCIL: - return WLR_TABLET_TOOL_TYPE_PENCIL; + *out = WLR_TABLET_TOOL_TYPE_PENCIL; + return true; case LIBINPUT_TABLET_TOOL_TYPE_AIRBRUSH: - return WLR_TABLET_TOOL_TYPE_AIRBRUSH; + *out = WLR_TABLET_TOOL_TYPE_AIRBRUSH; + return true; case LIBINPUT_TABLET_TOOL_TYPE_MOUSE: - return WLR_TABLET_TOOL_TYPE_MOUSE; + *out = WLR_TABLET_TOOL_TYPE_MOUSE; + return true; case LIBINPUT_TABLET_TOOL_TYPE_LENS: - return WLR_TABLET_TOOL_TYPE_LENS; + *out = WLR_TABLET_TOOL_TYPE_LENS; + return true; case LIBINPUT_TABLET_TOOL_TYPE_TOTEM: - return WLR_TABLET_TOOL_TYPE_TOTEM; + *out = WLR_TABLET_TOOL_TYPE_TOTEM; + return true; } - abort(); // unreachable + return false; +} + +static bool proximity_state_from_libinput(enum libinput_tablet_tool_proximity_state state, + enum wlr_tablet_tool_proximity_state *out) { + switch (state) { + case LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT: + *out = WLR_TABLET_TOOL_PROXIMITY_OUT; + return true; + case LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN: + *out = WLR_TABLET_TOOL_PROXIMITY_IN; + return true; + } + return false; +} + +static bool tip_state_from_libinput(enum libinput_tablet_tool_tip_state state, + enum wlr_tablet_tool_tip_state *out) { + switch (state) { + case LIBINPUT_TABLET_TOOL_TIP_UP: + *out = WLR_TABLET_TOOL_TIP_UP; + return true; + case LIBINPUT_TABLET_TOOL_TIP_DOWN: + *out = WLR_TABLET_TOOL_TIP_DOWN; + return true; + } + return false; } static struct tablet_tool *get_tablet_tool( @@ -110,14 +144,19 @@ static struct tablet_tool *get_tablet_tool( return tool; } + enum wlr_tablet_tool_type type; + if (!type_from_libinput(libinput_tablet_tool_get_type(libinput_tool), &type)) { + wlr_log(WLR_DEBUG, "Unhandled libinput tablet tool type"); + return NULL; + } + tool = calloc(1, sizeof(*tool)); if (tool == NULL) { wlr_log_errno(WLR_ERROR, "failed to allocate wlr_libinput_tablet_tool"); return NULL; } - tool->wlr_tool.type = wlr_type_from_libinput_type( - libinput_tablet_tool_get_type(libinput_tool)); + tool->wlr_tool.type = type; tool->wlr_tool.hardware_serial = libinput_tablet_tool_get_serial(libinput_tool); tool->wlr_tool.hardware_wacom = @@ -209,14 +248,12 @@ void handle_tablet_tool_proximity(struct libinput_event *event, .y = libinput_event_tablet_tool_get_y_transformed(tevent, 1), }; - switch (libinput_event_tablet_tool_get_proximity_state(tevent)) { - case LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT: - wlr_event.state = WLR_TABLET_TOOL_PROXIMITY_OUT; - break; - case LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN: - wlr_event.state = WLR_TABLET_TOOL_PROXIMITY_IN; - break; + if (!proximity_state_from_libinput(libinput_event_tablet_tool_get_proximity_state(tevent), + &wlr_event.state)) { + wlr_log(WLR_DEBUG, "Unhandled libinput tablet tool proximity state"); + return; } + wl_signal_emit_mutable(&wlr_tablet->events.proximity, &wlr_event); if (libinput_event_tablet_tool_get_proximity_state(tevent) == @@ -251,14 +288,11 @@ void handle_tablet_tool_tip(struct libinput_event *event, .y = libinput_event_tablet_tool_get_y_transformed(tevent, 1), }; - switch (libinput_event_tablet_tool_get_tip_state(tevent)) { - case LIBINPUT_TABLET_TOOL_TIP_UP: - wlr_event.state = WLR_TABLET_TOOL_TIP_UP; - break; - case LIBINPUT_TABLET_TOOL_TIP_DOWN: - wlr_event.state = WLR_TABLET_TOOL_TIP_DOWN; - break; + if (!tip_state_from_libinput(libinput_event_tablet_tool_get_tip_state(tevent), &wlr_event.state)) { + wlr_log(WLR_DEBUG, "Unhandled libinput tablet tool tip state"); + return; } + wl_signal_emit_mutable(&wlr_tablet->events.tip, &wlr_event); } @@ -277,13 +311,11 @@ void handle_tablet_tool_button(struct libinput_event *event, .time_msec = usec_to_msec(libinput_event_tablet_tool_get_time_usec(tevent)), .button = libinput_event_tablet_tool_get_button(tevent), }; - switch (libinput_event_tablet_tool_get_button_state(tevent)) { - case LIBINPUT_BUTTON_STATE_RELEASED: - wlr_event.state = WLR_BUTTON_RELEASED; - break; - case LIBINPUT_BUTTON_STATE_PRESSED: - wlr_event.state = WLR_BUTTON_PRESSED; - break; + + if (!button_state_from_libinput(libinput_event_tablet_tool_get_button_state(tevent), &wlr_event.state)) { + wlr_log(WLR_DEBUG, "Unhandled libinput button state"); + return; } + wl_signal_emit_mutable(&wlr_tablet->events.button, &wlr_event); } diff --git a/include/backend/libinput.h b/include/backend/libinput.h index 874e9aa1f..e7123884e 100644 --- a/include/backend/libinput.h +++ b/include/backend/libinput.h @@ -132,4 +132,6 @@ void handle_tablet_pad_ring(struct libinput_event *event, void handle_tablet_pad_strip(struct libinput_event *event, struct wlr_tablet_pad *tablet_pad); +bool button_state_from_libinput(enum libinput_button_state state, enum wlr_button_state *out); + #endif From 1efb216c6d85ec0d311d8e8f8ce7cf0ac0f840ae Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Thu, 19 Feb 2026 14:17:33 +0100 Subject: [PATCH 257/311] backend/drm: Close non-master drm fd on failure If we are not able to prepare the fd for non-master usage, close the fd before returning an error. --- backend/drm/drm.c | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/drm/drm.c b/backend/drm/drm.c index 86b52c684..bd0147a42 100644 --- a/backend/drm/drm.c +++ b/backend/drm/drm.c @@ -2104,6 +2104,7 @@ int wlr_drm_backend_get_non_master_fd(struct wlr_backend *backend) { if (drmIsMaster(fd) && drmDropMaster(fd) < 0) { wlr_log_errno(WLR_ERROR, "Failed to drop master"); + close(fd); return -1; } From 25bec59c75e7f6c95d29686915d1ddd0c62ab539 Mon Sep 17 00:00:00 2001 From: hrdl Date: Thu, 19 Feb 2026 22:53:47 +0100 Subject: [PATCH 258/311] CONTRIBUTING.md: update git host gitlab.freedesktop.org -> ssh.gitlab.freedesktop.org --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index aea7d6960..d9c49f7c2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -22,7 +22,7 @@ don't, however, allow me to make a suggestion: feature branches pulled from upstream. Try this: 1. Fork wlroots -2. `git clone git@gitlab.freedesktop.org:/wlroots.git && cd wlroots` +2. `git clone git@ssh.gitlab.freedesktop.org:/wlroots.git && cd wlroots` 3. `git remote add upstream https://gitlab.freedesktop.org/wlroots/wlroots.git` You only need to do this once. You're never going to use your fork's master From 7be5e3689c02e5105e6fbef2d5539ffe21775056 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sun, 22 Feb 2026 23:38:52 +0100 Subject: [PATCH 259/311] build: bump version to 0.20.0-rc3 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index b5b756bae..f054cc250 100644 --- a/meson.build +++ b/meson.build @@ -1,7 +1,7 @@ project( 'wlroots', 'c', - version: '0.20.0-rc2', + version: '0.20.0-rc3', license: 'MIT', meson_version: '>=1.3', default_options: [ From a6e5807e8640c038f0b3d7b0a9e01826eb81a101 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sat, 14 Feb 2026 17:53:54 +0100 Subject: [PATCH 260/311] render/vulkan: introduce buffer_import_sync_file() Will be used in two spots in a following commit. --- render/vulkan/renderer.c | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/render/vulkan/renderer.c b/render/vulkan/renderer.c index 7790d40b2..352e5f344 100644 --- a/render/vulkan/renderer.c +++ b/render/vulkan/renderer.c @@ -1013,6 +1013,23 @@ bool vulkan_sync_foreign_texture(struct wlr_vk_texture *texture, return true; } +static bool buffer_import_sync_file(struct wlr_buffer *buffer, uint32_t flags, int sync_file_fd) { + struct wlr_dmabuf_attributes dmabuf = {0}; + if (!wlr_buffer_get_dmabuf(buffer, &dmabuf)) { + wlr_log(WLR_ERROR, "wlr_buffer_get_dmabuf() failed"); + return false; + } + + for (int i = 0; i < dmabuf.n_planes; i++) { + if (!dmabuf_import_sync_file(dmabuf.fd[i], flags, + sync_file_fd)) { + return false; + } + } + + return true; +} + bool vulkan_sync_render_buffer(struct wlr_vk_renderer *renderer, struct wlr_vk_render_buffer *render_buffer, struct wlr_vk_command_buffer *cb, struct wlr_drm_syncobj_timeline *signal_timeline, uint64_t signal_point) { @@ -1046,18 +1063,9 @@ bool vulkan_sync_render_buffer(struct wlr_vk_renderer *renderer, goto out; } } else { - struct wlr_dmabuf_attributes dmabuf = {0}; - if (!wlr_buffer_get_dmabuf(render_buffer->wlr_buffer, &dmabuf)) { - wlr_log(WLR_ERROR, "wlr_buffer_get_dmabuf failed"); + if (!buffer_import_sync_file(render_buffer->wlr_buffer, DMA_BUF_SYNC_WRITE, sync_file_fd)) { goto out; } - - for (int i = 0; i < dmabuf.n_planes; i++) { - if (!dmabuf_import_sync_file(dmabuf.fd[i], DMA_BUF_SYNC_WRITE, - sync_file_fd)) { - goto out; - } - } } ok = true; From 73bbad8433a916ca342f19975b7eb2aaf3a8fc6f Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sat, 14 Feb 2026 17:55:52 +0100 Subject: [PATCH 261/311] render/vulkan: take render pass in vulkan_sync_render_buffer() We'll need to grab textures from there in the next commit. Also rename it to better reflect what it does: synchronize release fences after a render pass has been submitted. --- include/render/vulkan.h | 5 ++--- render/vulkan/pass.c | 3 +-- render/vulkan/renderer.c | 16 ++++++++-------- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/include/render/vulkan.h b/include/render/vulkan.h index 2f8d9d241..6e1e721d1 100644 --- a/include/render/vulkan.h +++ b/include/render/vulkan.h @@ -473,9 +473,8 @@ void vulkan_reset_command_buffer(struct wlr_vk_command_buffer *cb); bool vulkan_wait_command_buffer(struct wlr_vk_command_buffer *cb, struct wlr_vk_renderer *renderer); -bool vulkan_sync_render_buffer(struct wlr_vk_renderer *renderer, - struct wlr_vk_render_buffer *render_buffer, struct wlr_vk_command_buffer *cb, - struct wlr_drm_syncobj_timeline *signal_timeline, uint64_t signal_point); +bool vulkan_sync_render_pass_release(struct wlr_vk_renderer *renderer, + struct wlr_vk_render_pass *pass); bool vulkan_sync_foreign_texture(struct wlr_vk_texture *texture, int sync_file_fds[static WLR_DMABUF_MAX_PLANES]); diff --git a/render/vulkan/pass.c b/render/vulkan/pass.c index 271b426cc..44dc68553 100644 --- a/render/vulkan/pass.c +++ b/render/vulkan/pass.c @@ -606,8 +606,7 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { wl_list_insert(&stage_cb->stage_buffers, &stage_buf->link); } - if (!vulkan_sync_render_buffer(renderer, render_buffer, render_cb, - pass->signal_timeline, pass->signal_point)) { + if (!vulkan_sync_render_pass_release(renderer, pass)) { wlr_log(WLR_ERROR, "Failed to sync render buffer"); } diff --git a/render/vulkan/renderer.c b/render/vulkan/renderer.c index 352e5f344..5ca9f7a7d 100644 --- a/render/vulkan/renderer.c +++ b/render/vulkan/renderer.c @@ -1030,12 +1030,12 @@ static bool buffer_import_sync_file(struct wlr_buffer *buffer, uint32_t flags, i return true; } -bool vulkan_sync_render_buffer(struct wlr_vk_renderer *renderer, - struct wlr_vk_render_buffer *render_buffer, struct wlr_vk_command_buffer *cb, - struct wlr_drm_syncobj_timeline *signal_timeline, uint64_t signal_point) { +bool vulkan_sync_render_pass_release(struct wlr_vk_renderer *renderer, + struct wlr_vk_render_pass *pass) { VkResult res; + struct wlr_vk_command_buffer *cb = pass->command_buffer; - if (!renderer->dev->implicit_sync_interop && signal_timeline == NULL) { + if (!renderer->dev->implicit_sync_interop && pass->signal_timeline == NULL) { // We have no choice but to block here sadly return vulkan_wait_command_buffer(cb, renderer); } @@ -1057,13 +1057,13 @@ bool vulkan_sync_render_buffer(struct wlr_vk_renderer *renderer, } bool ok = false; - if (signal_timeline != NULL) { - if (!wlr_drm_syncobj_timeline_import_sync_file(signal_timeline, - signal_point, sync_file_fd)) { + if (pass->signal_timeline != NULL) { + if (!wlr_drm_syncobj_timeline_import_sync_file(pass->signal_timeline, + pass->signal_point, sync_file_fd)) { goto out; } } else { - if (!buffer_import_sync_file(render_buffer->wlr_buffer, DMA_BUF_SYNC_WRITE, sync_file_fd)) { + if (!buffer_import_sync_file(pass->render_buffer->wlr_buffer, DMA_BUF_SYNC_WRITE, sync_file_fd)) { goto out; } } From 43b37e34d662ca893ebaae6e813634ee216c352f Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sat, 14 Feb 2026 17:57:42 +0100 Subject: [PATCH 262/311] render/vulkan: fix missing DMA-BUF implicit read fence for textures When we're reading from a DMA-BUF texture using implicit sync, we need to (1) wait for any writer to be done and (2) prevent any writers from mutating the texture while we're still reading. We were doing (1) but not (2). Fix this by calling dmabuf_import_sync_file() with DMA_BUF_SYNC_READ for all DMA-BUF textures we've used in the render pass. --- render/vulkan/renderer.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/render/vulkan/renderer.c b/render/vulkan/renderer.c index 5ca9f7a7d..15ed7e17f 100644 --- a/render/vulkan/renderer.c +++ b/render/vulkan/renderer.c @@ -1066,6 +1066,13 @@ bool vulkan_sync_render_pass_release(struct wlr_vk_renderer *renderer, if (!buffer_import_sync_file(pass->render_buffer->wlr_buffer, DMA_BUF_SYNC_WRITE, sync_file_fd)) { goto out; } + + struct wlr_vk_render_pass_texture *pass_texture; + wl_array_for_each(pass_texture, &pass->textures) { + if (!buffer_import_sync_file(pass_texture->texture->buffer, DMA_BUF_SYNC_READ, sync_file_fd)) { + goto out; + } + } } ok = true; From ff4ce121796081dec656fc4106dbf6b6a043fa77 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sat, 14 Feb 2026 18:04:58 +0100 Subject: [PATCH 263/311] render/vulkan: introduce buffer_export_sync_file() Same as buffer_import_sync_file(), but for the export side. --- render/vulkan/renderer.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/render/vulkan/renderer.c b/render/vulkan/renderer.c index 15ed7e17f..8f5ad85b5 100644 --- a/render/vulkan/renderer.c +++ b/render/vulkan/renderer.c @@ -968,13 +968,11 @@ static struct wlr_vk_render_buffer *get_render_buffer( return buffer; } -bool vulkan_sync_foreign_texture(struct wlr_vk_texture *texture, - int sync_file_fds[static WLR_DMABUF_MAX_PLANES]) { - struct wlr_vk_renderer *renderer = texture->renderer; - +static bool buffer_export_sync_file(struct wlr_vk_renderer *renderer, struct wlr_buffer *buffer, + uint32_t flags, int sync_file_fds[static WLR_DMABUF_MAX_PLANES]) { struct wlr_dmabuf_attributes dmabuf = {0}; - if (!wlr_buffer_get_dmabuf(texture->buffer, &dmabuf)) { - wlr_log(WLR_ERROR, "Failed to get texture DMA-BUF"); + if (!wlr_buffer_get_dmabuf(buffer, &dmabuf)) { + wlr_log(WLR_ERROR, "wlr_buffer_get_dmabuf() failed"); return false; } @@ -984,7 +982,7 @@ bool vulkan_sync_foreign_texture(struct wlr_vk_texture *texture, for (int i = 0; i < dmabuf.n_planes; i++) { struct pollfd pollfd = { .fd = dmabuf.fd[i], - .events = POLLIN, + .events = (flags & DMA_BUF_SYNC_WRITE) ? POLLOUT : POLLIN, }; int timeout_ms = 1000; int ret = poll(&pollfd, 1, timeout_ms); @@ -1001,7 +999,7 @@ bool vulkan_sync_foreign_texture(struct wlr_vk_texture *texture, } for (int i = 0; i < dmabuf.n_planes; i++) { - int sync_file_fd = dmabuf_export_sync_file(dmabuf.fd[i], DMA_BUF_SYNC_READ); + int sync_file_fd = dmabuf_export_sync_file(dmabuf.fd[i], flags); if (sync_file_fd < 0) { wlr_log(WLR_ERROR, "Failed to extract DMA-BUF fence"); return false; @@ -1013,6 +1011,11 @@ bool vulkan_sync_foreign_texture(struct wlr_vk_texture *texture, return true; } +bool vulkan_sync_foreign_texture(struct wlr_vk_texture *texture, + int sync_file_fds[static WLR_DMABUF_MAX_PLANES]) { + return buffer_export_sync_file(texture->renderer, texture->buffer, DMA_BUF_SYNC_READ, sync_file_fds); +} + static bool buffer_import_sync_file(struct wlr_buffer *buffer, uint32_t flags, int sync_file_fd) { struct wlr_dmabuf_attributes dmabuf = {0}; if (!wlr_buffer_get_dmabuf(buffer, &dmabuf)) { From 8c8d6363a19e04054071f6983423c4eb760bf86b Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sat, 14 Feb 2026 18:06:40 +0100 Subject: [PATCH 264/311] render/vulkan: add "acquire" to vulkan_sync_foreign_texture() Makes it more obvious that this is about the acquire side, not the release side. --- include/render/vulkan.h | 2 +- render/vulkan/pass.c | 2 +- render/vulkan/renderer.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/render/vulkan.h b/include/render/vulkan.h index 6e1e721d1..a1d1c2646 100644 --- a/include/render/vulkan.h +++ b/include/render/vulkan.h @@ -475,7 +475,7 @@ bool vulkan_wait_command_buffer(struct wlr_vk_command_buffer *cb, bool vulkan_sync_render_pass_release(struct wlr_vk_renderer *renderer, struct wlr_vk_render_pass *pass); -bool vulkan_sync_foreign_texture(struct wlr_vk_texture *texture, +bool vulkan_sync_foreign_texture_acquire(struct wlr_vk_texture *texture, int sync_file_fds[static WLR_DMABUF_MAX_PLANES]); bool vulkan_read_pixels(struct wlr_vk_renderer *vk_renderer, diff --git a/render/vulkan/pass.c b/render/vulkan/pass.c index 44dc68553..0539bae1d 100644 --- a/render/vulkan/pass.c +++ b/render/vulkan/pass.c @@ -386,7 +386,7 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { sync_file_fds[0] = sync_file_fd; } else { struct wlr_vk_texture *texture = pass_texture->texture; - if (!vulkan_sync_foreign_texture(texture, sync_file_fds)) { + if (!vulkan_sync_foreign_texture_acquire(texture, sync_file_fds)) { wlr_log(WLR_ERROR, "Failed to wait for foreign texture DMA-BUF fence"); continue; } diff --git a/render/vulkan/renderer.c b/render/vulkan/renderer.c index 8f5ad85b5..feafcbc34 100644 --- a/render/vulkan/renderer.c +++ b/render/vulkan/renderer.c @@ -1011,7 +1011,7 @@ static bool buffer_export_sync_file(struct wlr_vk_renderer *renderer, struct wlr return true; } -bool vulkan_sync_foreign_texture(struct wlr_vk_texture *texture, +bool vulkan_sync_foreign_texture_acquire(struct wlr_vk_texture *texture, int sync_file_fds[static WLR_DMABUF_MAX_PLANES]) { return buffer_export_sync_file(texture->renderer, texture->buffer, DMA_BUF_SYNC_READ, sync_file_fds); } From 2367d78c3c1de2428cbbf9444d6eff632698baf6 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sat, 14 Feb 2026 18:17:32 +0100 Subject: [PATCH 265/311] render/vulkan: fix missing DMA-BUF implicit write fence for render buffer Same as previous commit for the read side, but this one waits for all readers to be done before starting to write. --- include/render/vulkan.h | 2 ++ render/vulkan/pass.c | 40 +++++++++++++++++++++++++++++++++++++++- render/vulkan/renderer.c | 6 ++++++ 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/include/render/vulkan.h b/include/render/vulkan.h index a1d1c2646..903d1c33e 100644 --- a/include/render/vulkan.h +++ b/include/render/vulkan.h @@ -477,6 +477,8 @@ bool vulkan_sync_render_pass_release(struct wlr_vk_renderer *renderer, struct wlr_vk_render_pass *pass); bool vulkan_sync_foreign_texture_acquire(struct wlr_vk_texture *texture, int sync_file_fds[static WLR_DMABUF_MAX_PLANES]); +bool vulkan_sync_render_buffer_acquire(struct wlr_vk_render_buffer *render_buffer, + int sync_file_fds[static WLR_DMABUF_MAX_PLANES]); bool vulkan_read_pixels(struct wlr_vk_renderer *vk_renderer, VkFormat src_format, VkImage src_image, diff --git a/render/vulkan/pass.c b/render/vulkan/pass.c index 0539bae1d..503e37c07 100644 --- a/render/vulkan/pass.c +++ b/render/vulkan/pass.c @@ -146,6 +146,40 @@ static VkSemaphore render_pass_wait_sync_file(struct wlr_vk_render_pass *pass, return *sem_ptr; } +static bool render_pass_wait_render_buffer(struct wlr_vk_render_pass *pass, + VkSemaphoreSubmitInfoKHR *render_wait, uint32_t *render_wait_len_ptr) { + int sync_file_fds[WLR_DMABUF_MAX_PLANES]; + for (size_t i = 0; i < WLR_DMABUF_MAX_PLANES; i++) { + sync_file_fds[i] = -1; + } + + if (!vulkan_sync_render_buffer_acquire(pass->render_buffer, sync_file_fds)) { + return false; + } + + for (size_t i = 0; i < WLR_DMABUF_MAX_PLANES; i++) { + if (sync_file_fds[i] < 0) { + continue; + } + + VkSemaphore sem = render_pass_wait_sync_file(pass, *render_wait_len_ptr, sync_file_fds[i]); + if (sem == VK_NULL_HANDLE) { + close(sync_file_fds[i]); + continue; + } + + render_wait[*render_wait_len_ptr] = (VkSemaphoreSubmitInfoKHR){ + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO_KHR, + .semaphore = sem, + .stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT_KHR, + }; + + (*render_wait_len_ptr)++; + } + + return true; +} + static bool unwrap_color_transform(struct wlr_color_transform *transform, float matrix[static 9], enum wlr_color_transfer_function *tf) { if (transform == NULL) { @@ -308,7 +342,7 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { vkCmdEndRenderPass(render_cb->vk); size_t pass_textures_len = pass->textures.size / sizeof(struct wlr_vk_render_pass_texture); - size_t render_wait_cap = pass_textures_len * WLR_DMABUF_MAX_PLANES; + size_t render_wait_cap = (1 + pass_textures_len) * WLR_DMABUF_MAX_PLANES; render_wait = calloc(render_wait_cap, sizeof(*render_wait)); if (render_wait == NULL) { wlr_log_errno(WLR_ERROR, "Allocation failed"); @@ -413,6 +447,10 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { } } + if (!render_pass_wait_render_buffer(pass, render_wait, &render_wait_len)) { + wlr_log(WLR_ERROR, "Failed to wait for render buffer DMA-BUF fence"); + } + // also add acquire/release barriers for the current render buffer VkImageLayout src_layout = VK_IMAGE_LAYOUT_GENERAL; if (!pass->render_buffer_out->transitioned) { diff --git a/render/vulkan/renderer.c b/render/vulkan/renderer.c index feafcbc34..da17a4703 100644 --- a/render/vulkan/renderer.c +++ b/render/vulkan/renderer.c @@ -1016,6 +1016,12 @@ bool vulkan_sync_foreign_texture_acquire(struct wlr_vk_texture *texture, return buffer_export_sync_file(texture->renderer, texture->buffer, DMA_BUF_SYNC_READ, sync_file_fds); } +bool vulkan_sync_render_buffer_acquire(struct wlr_vk_render_buffer *render_buffer, + int sync_file_fds[static WLR_DMABUF_MAX_PLANES]) { + return buffer_export_sync_file(render_buffer->renderer, render_buffer->wlr_buffer, + DMA_BUF_SYNC_WRITE, sync_file_fds); +} + static bool buffer_import_sync_file(struct wlr_buffer *buffer, uint32_t flags, int sync_file_fd) { struct wlr_dmabuf_attributes dmabuf = {0}; if (!wlr_buffer_get_dmabuf(buffer, &dmabuf)) { From 1ce992d7cb5023a7a7c91ed6ce156529e3709657 Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Tue, 24 Feb 2026 11:52:00 +0100 Subject: [PATCH 266/311] scene/layer_shell_v1: Add support for exclusive_edge The v5 layer shell interface allows the client to specify which edge the exclusive zone will apply to, instead of deducing it from the anchor points. Add support for this to the layer shell scene helper. --- types/scene/layer_shell_v1.c | 33 +++++++++++---------------------- 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/types/scene/layer_shell_v1.c b/types/scene/layer_shell_v1.c index 4ae736ddf..234227df1 100644 --- a/types/scene/layer_shell_v1.c +++ b/types/scene/layer_shell_v1.c @@ -1,6 +1,7 @@ #include #include #include +#include static void scene_layer_surface_handle_tree_destroy( struct wl_listener *listener, void *data) { @@ -21,36 +22,23 @@ static void scene_layer_surface_handle_layer_surface_destroy( static void layer_surface_exclusive_zone( struct wlr_layer_surface_v1_state *state, + enum wlr_edges edge, struct wlr_box *usable_area) { - switch (state->anchor) { - case ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP: - case (ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | - ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | - ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT): - // Anchor top + switch (edge) { + case WLR_EDGE_NONE: + return; + case WLR_EDGE_TOP: usable_area->y += state->exclusive_zone + state->margin.top; usable_area->height -= state->exclusive_zone + state->margin.top; break; - case ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM: - case (ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM | - ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | - ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT): - // Anchor bottom + case WLR_EDGE_BOTTOM: usable_area->height -= state->exclusive_zone + state->margin.bottom; break; - case ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT: - case (ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | - ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM | - ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT): - // Anchor left + case WLR_EDGE_LEFT: usable_area->x += state->exclusive_zone + state->margin.left; usable_area->width -= state->exclusive_zone + state->margin.left; break; - case ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT: - case (ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | - ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM | - ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT): - // Anchor right + case WLR_EDGE_RIGHT: usable_area->width -= state->exclusive_zone + state->margin.right; break; } @@ -121,7 +109,8 @@ void wlr_scene_layer_surface_v1_configure( wlr_layer_surface_v1_configure(layer_surface, box.width, box.height); if (layer_surface->surface->mapped && state->exclusive_zone > 0) { - layer_surface_exclusive_zone(state, usable_area); + enum wlr_edges edge = wlr_layer_surface_v1_get_exclusive_edge(layer_surface); + layer_surface_exclusive_zone(state, edge, usable_area); } } From 6d9aa17572c12cc22d2c9f7f0afc3e942a699d2a Mon Sep 17 00:00:00 2001 From: Diego Viola Date: Sat, 28 Feb 2026 04:26:42 -0300 Subject: [PATCH 267/311] treewide: fix typos Signed-off-by: Diego Viola --- backend/drm/drm.c | 2 +- backend/wayland/seat.c | 2 +- include/render/color.h | 2 +- include/render/vulkan.h | 2 +- include/wlr/xcursor.h | 2 +- render/egl.c | 2 +- render/pixman/pass.c | 2 +- render/vulkan/shaders/common.vert | 2 +- render/vulkan/texture.c | 2 +- render/vulkan/vulkan.c | 2 +- tinywl/tinywl.c | 6 +++--- types/scene/wlr_scene.c | 2 +- util/array.c | 2 +- 13 files changed, 15 insertions(+), 15 deletions(-) diff --git a/backend/drm/drm.c b/backend/drm/drm.c index bd0147a42..fe9dcc341 100644 --- a/backend/drm/drm.c +++ b/backend/drm/drm.c @@ -2139,7 +2139,7 @@ struct wlr_drm_lease *wlr_drm_create_lease(struct wlr_output **outputs, wlr_log(WLR_DEBUG, "Connector %d", conn->id); if (!drm_connector_alloc_crtc(conn)) { - wlr_log(WLR_ERROR, "Failled to allocate connector CRTC"); + wlr_log(WLR_ERROR, "Failed to allocate connector CRTC"); return NULL; } diff --git a/backend/wayland/seat.c b/backend/wayland/seat.c index a66357bf8..f178d9baf 100644 --- a/backend/wayland/seat.c +++ b/backend/wayland/seat.c @@ -246,7 +246,7 @@ void init_seat_touch(struct wlr_wl_seat *seat) { struct wlr_wl_output *output; wl_list_for_each(output, &seat->backend->outputs, link) { - /* Multi-output touch not supproted */ + /* Multi-output touch not supported */ seat->wlr_touch.output_name = strdup(output->wlr_output.name); break; } diff --git a/include/render/color.h b/include/render/color.h index 57d2b6a96..60c841d12 100644 --- a/include/render/color.h +++ b/include/render/color.h @@ -91,7 +91,7 @@ struct wlr_color_transform_lut_3x1d *color_transform_lut_3x1d_from_base( * Create a simplified / normalized wlr_color_transform pipeline. * `transforms` may contain NULL transforms, they will be interpreted as the * identity transform, and removed. - * `*result` may be set to a tranform of a type different from + * `*result` may be set to a transform of a type different from * `wlr_color_transform_pipeline`, or to NULL if all input transforms are NULL */ bool color_transform_compose(struct wlr_color_transform **result, diff --git a/include/render/vulkan.h b/include/render/vulkan.h index 903d1c33e..f96619f2c 100644 --- a/include/render/vulkan.h +++ b/include/render/vulkan.h @@ -224,7 +224,7 @@ struct wlr_vk_render_buffer_out { bool transitioned; }; -// Renderer-internal represenation of an wlr_buffer imported for rendering. +// Renderer-internal representation of an wlr_buffer imported for rendering. struct wlr_vk_render_buffer { struct wlr_buffer *wlr_buffer; struct wlr_addon addon; diff --git a/include/wlr/xcursor.h b/include/wlr/xcursor.h index 7cf333d70..5e9c140ae 100644 --- a/include/wlr/xcursor.h +++ b/include/wlr/xcursor.h @@ -47,7 +47,7 @@ /** * A still cursor image. * - * The buffer contains pixels layed out in a packed DRM_FORMAT_ARGB8888 format. + * The buffer contains pixels laid out in a packed DRM_FORMAT_ARGB8888 format. */ struct wlr_xcursor_image { uint32_t width; /* actual width */ diff --git a/render/egl.c b/render/egl.c index 6f3e9c8ca..8673acabf 100644 --- a/render/egl.c +++ b/render/egl.c @@ -156,7 +156,7 @@ static void init_dmabuf_formats(struct wlr_egl *egl) { } if (modifiers_len == 0) { - // Asume the linear layout is supported if the driver doesn't + // Assume the linear layout is supported if the driver doesn't // explicitly say otherwise wlr_drm_format_set_add(&egl->dmabuf_texture_formats, fmt, DRM_FORMAT_MOD_LINEAR); diff --git a/render/pixman/pass.c b/render/pixman/pass.c index 4ae742cab..d3ee17dca 100644 --- a/render/pixman/pass.c +++ b/render/pixman/pass.c @@ -78,7 +78,7 @@ static void render_pass_add_texture(struct wlr_render_pass *wlr_pass, if (options->transform != WL_OUTPUT_TRANSFORM_NORMAL || src_box_transformed.width != dst_box.width || src_box_transformed.height != dst_box.height) { - // Cosinus/sinus values are extact integers for enum wl_output_transform entries + // Cosinus/sinus values are exact integers for enum wl_output_transform entries int tr_cos = 1, tr_sin = 0, tr_x = 0, tr_y = 0; switch (options->transform) { case WL_OUTPUT_TRANSFORM_NORMAL: diff --git a/render/vulkan/shaders/common.vert b/render/vulkan/shaders/common.vert index c6175d248..f1579790d 100644 --- a/render/vulkan/shaders/common.vert +++ b/render/vulkan/shaders/common.vert @@ -1,7 +1,7 @@ #version 450 // we use a mat4 since it uses the same size as mat3 due to -// alignment. Easier to deal with (tighly-packed) mat4 though. +// alignment. Easier to deal with (tightly-packed) mat4 though. layout(push_constant, row_major) uniform UBO { mat4 proj; vec2 uv_offset; diff --git a/render/vulkan/texture.c b/render/vulkan/texture.c index ffea87795..57db97a56 100644 --- a/render/vulkan/texture.c +++ b/render/vulkan/texture.c @@ -189,7 +189,7 @@ void vulkan_texture_destroy(struct wlr_vk_texture *texture) { // when we recorded a command to fill this image _this_ frame, // it has to be executed before the texture can be destroyed. // Add it to the renderer->destroy_textures list, destroying - // _after_ the stage command buffer has exectued + // _after_ the stage command buffer has executed if (texture->last_used_cb != NULL) { assert(texture->destroy_link.next == NULL); // not already inserted wl_list_insert(&texture->last_used_cb->destroy_textures, diff --git a/render/vulkan/vulkan.c b/render/vulkan/vulkan.c index 78bc25941..ee7adc011 100644 --- a/render/vulkan/vulkan.c +++ b/render/vulkan/vulkan.c @@ -290,7 +290,7 @@ VkPhysicalDevice vulkan_find_drm_phdev(struct wlr_vk_instance *ini, int drm_fd) log_phdev(&phdev_props); if (phdev_props.apiVersion < VK_API_VERSION_1_1) { - // NOTE: we could additionaly check whether the + // NOTE: we could additionally check whether the // VkPhysicalDeviceProperties2KHR extension is supported but // implementations not supporting 1.1 are unlikely in future continue; diff --git a/tinywl/tinywl.c b/tinywl/tinywl.c index b3d902c7e..fe242e1a9 100644 --- a/tinywl/tinywl.c +++ b/tinywl/tinywl.c @@ -614,7 +614,7 @@ static void server_new_output(struct wl_listener *listener, void *data) { struct wlr_output *wlr_output = data; /* Configures the output created by the backend to use our allocator - * and our renderer. Must be done once, before commiting the output */ + * and our renderer. Must be done once, before committing the output */ wlr_output_init_render(wlr_output, server->allocator, server->renderer); /* The output may be disabled, switch it on. */ @@ -723,7 +723,7 @@ static void xdg_toplevel_destroy(struct wl_listener *listener, void *data) { static void begin_interactive(struct tinywl_toplevel *toplevel, enum tinywl_cursor_mode mode, uint32_t edges) { /* This function sets up an interactive move or resize operation, where the - * compositor stops propegating pointer events to clients and instead + * compositor stops propagating pointer events to clients and instead * consumes them itself, to move or resize windows. */ struct tinywl_server *server = toplevel->server; @@ -906,7 +906,7 @@ int main(int argc, char *argv[]) { struct tinywl_server server = {0}; /* The Wayland display is managed by libwayland. It handles accepting - * clients from the Unix socket, manging Wayland globals, and so on. */ + * clients from the Unix socket, managing Wayland globals, and so on. */ server.wl_display = wl_display_create(); /* The backend is a wlroots feature which abstracts the underlying input and * output hardware. The autocreate option will choose the most suitable diff --git a/types/scene/wlr_scene.c b/types/scene/wlr_scene.c index aaf9a8d74..51373e15a 100644 --- a/types/scene/wlr_scene.c +++ b/types/scene/wlr_scene.c @@ -2391,7 +2391,7 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, pixman_region32_init(&acc_damage); struct highlight_region *damage, *tmp_damage; wl_list_for_each_safe(damage, tmp_damage, regions, link) { - // remove overlaping damage regions + // remove overlapping damage regions pixman_region32_subtract(&damage->region, &damage->region, &acc_damage); pixman_region32_union(&acc_damage, &acc_damage, &damage->region); diff --git a/util/array.c b/util/array.c index ec16a7b13..d4771e817 100644 --- a/util/array.c +++ b/util/array.c @@ -14,7 +14,7 @@ bool array_realloc(struct wl_array *arr, size_t size) { // If the size is less than 1/4th of the allocation size, we shrink it. // 1/4th is picked to provide hysteresis, without which an array with size // arr->alloc would constantly reallocate if an element is added and then - // removed continously. + // removed continuously. size_t alloc; if (arr->alloc > 0 && size > arr->alloc / 4) { alloc = arr->alloc; From a55b85e2e1138f9534890e4dc6a224ec2efb9d4d Mon Sep 17 00:00:00 2001 From: Wang Yu Date: Tue, 3 Mar 2026 14:37:42 +0800 Subject: [PATCH 268/311] xwayland: fix memory leak on pipe() failure When pipe() fails in xwm_selection_send_data(), the function returns without cleaning up the allocated transfer structure and initialized wl_array. This causes a memory leak. Add wl_array_release() and free() to clean up resources when pipe() fails. Signed-off-by: Wang Yu --- xwayland/selection/outgoing.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/xwayland/selection/outgoing.c b/xwayland/selection/outgoing.c index 795a4768b..6216abbb4 100644 --- a/xwayland/selection/outgoing.c +++ b/xwayland/selection/outgoing.c @@ -283,6 +283,8 @@ static bool xwm_selection_send_data(struct wlr_xwm_selection *selection, int p[2]; if (pipe(p) == -1) { wlr_log_errno(WLR_ERROR, "pipe() failed"); + wl_array_release(&transfer->source_data); + free(transfer); return false; } From 55bb69e2c4443d9ab2672ed9e4dfe10cca9f2663 Mon Sep 17 00:00:00 2001 From: YaoBing Xiao Date: Fri, 6 Feb 2026 23:30:35 +0800 Subject: [PATCH 269/311] render/gles: use optimized clears for unblended rects --- render/gles2/pass.c | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/render/gles2/pass.c b/render/gles2/pass.c index b10ac047d..a70ea1320 100644 --- a/render/gles2/pass.c +++ b/render/gles2/pass.c @@ -260,17 +260,26 @@ static void render_pass_add_rect(struct wlr_render_pass *wlr_pass, const struct wlr_render_color *color = &options->color; struct wlr_box box; - wlr_render_rect_options_get_box(options, pass->buffer->buffer, &box); + struct wlr_buffer *wlr_buffer = pass->buffer->buffer; + wlr_render_rect_options_get_box(options, wlr_buffer, &box); push_gles2_debug(renderer); - setup_blending(color->a == 1.0 ? WLR_RENDER_BLEND_MODE_NONE : options->blend_mode); - - glUseProgram(renderer->shaders.quad.program); - - set_proj_matrix(renderer->shaders.quad.proj, pass->projection_matrix, &box); - glUniform4f(renderer->shaders.quad.color, color->r, color->g, color->b, color->a); - - render(&box, options->clip, renderer->shaders.quad.pos_attrib); + enum wlr_render_blend_mode blend_mode = + color->a == 1.0 ? WLR_RENDER_BLEND_MODE_NONE : options->blend_mode; + if (blend_mode == WLR_RENDER_BLEND_MODE_NONE && + options->clip == NULL && + box.x == 0 && box.y == 0 && + box.width == wlr_buffer->width && + box.height == wlr_buffer->height) { + glClearColor(color->r, color->g, color->b, color->a); + glClear(GL_COLOR_BUFFER_BIT); + } else { + setup_blending(blend_mode); + glUseProgram(renderer->shaders.quad.program); + set_proj_matrix(renderer->shaders.quad.proj, pass->projection_matrix, &box); + glUniform4f(renderer->shaders.quad.color, color->r, color->g, color->b, color->a); + render(&box, options->clip, renderer->shaders.quad.pos_attrib); + } pop_gles2_debug(renderer); } From 1b8c3ea8c5f084f9844476c7a8491951c5646d47 Mon Sep 17 00:00:00 2001 From: liupeng Date: Wed, 4 Mar 2026 09:45:15 +0800 Subject: [PATCH 270/311] screencopy: simplify capture error handling Signed-off-by: liupeng --- types/wlr_screencopy_v1.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/types/wlr_screencopy_v1.c b/types/wlr_screencopy_v1.c index 58e8dc8ba..9916a604c 100644 --- a/types/wlr_screencopy_v1.c +++ b/types/wlr_screencopy_v1.c @@ -505,7 +505,7 @@ static void capture_output(struct wl_client *wl_client, wl_resource_set_implementation(frame->resource, &frame_impl, frame, frame_handle_resource_destroy); - if (output == NULL) { + if (output == NULL || !output->enabled) { wl_resource_set_user_data(frame->resource, NULL); zwlr_screencopy_frame_v1_send_failed(frame->resource); free(frame); @@ -522,10 +522,6 @@ static void capture_output(struct wl_client *wl_client, wl_signal_add(&output->events.destroy, &frame->output_destroy); frame->output_destroy.notify = frame_handle_output_destroy; - if (output == NULL || !output->enabled) { - goto error; - } - struct wlr_renderer *renderer = output->renderer; assert(renderer); From 2d3916614600f3df63257a8db8edb2e32a021916 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Thu, 5 Mar 2026 19:54:06 +0100 Subject: [PATCH 271/311] build: bump version to 0.20.0-rc4 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index f054cc250..d9e215afd 100644 --- a/meson.build +++ b/meson.build @@ -1,7 +1,7 @@ project( 'wlroots', 'c', - version: '0.20.0-rc3', + version: '0.20.0-rc4', license: 'MIT', meson_version: '>=1.3', default_options: [ From 3336d28813698eb6fba908a4dbcf38b79ed2e3db Mon Sep 17 00:00:00 2001 From: Andri Yngvason Date: Thu, 5 Mar 2026 10:23:06 +0000 Subject: [PATCH 272/311] image_capture_source/output: Update constraints on enable Without observing the enable event, clients receive no pixel formats and buffer dimensions are reported as 0 after an output has been re-enabled. --- types/ext_image_capture_source_v1/output.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/types/ext_image_capture_source_v1/output.c b/types/ext_image_capture_source_v1/output.c index 75fa15f4d..0e3a57823 100644 --- a/types/ext_image_capture_source_v1/output.c +++ b/types/ext_image_capture_source_v1/output.c @@ -110,6 +110,10 @@ static const struct wlr_ext_image_capture_source_v1_interface output_source_impl static void source_update_buffer_constraints(struct wlr_ext_output_image_capture_source_v1 *source) { struct wlr_output *output = source->output; + if (!output->enabled) { + return; + } + if (!wlr_output_configure_primary_swapchain(output, NULL, &output->swapchain)) { return; } @@ -123,7 +127,8 @@ static void source_handle_output_commit(struct wl_listener *listener, struct wlr_ext_output_image_capture_source_v1 *source = wl_container_of(listener, source, output_commit); struct wlr_output_event_commit *event = data; - if (event->state->committed & (WLR_OUTPUT_STATE_MODE | WLR_OUTPUT_STATE_RENDER_FORMAT)) { + if (event->state->committed & (WLR_OUTPUT_STATE_MODE | + WLR_OUTPUT_STATE_RENDER_FORMAT | WLR_OUTPUT_STATE_ENABLED)) { source_update_buffer_constraints(source); } From 14b3c96c1e4d5d9952b2e59c0bb3283707610930 Mon Sep 17 00:00:00 2001 From: YaoBing Xiao Date: Fri, 6 Mar 2026 15:59:18 +0800 Subject: [PATCH 273/311] treewide: make type-check helpers take const pointers --- backend/drm/backend.c | 2 +- backend/drm/drm.c | 2 +- backend/headless/backend.c | 2 +- backend/headless/output.c | 2 +- backend/libinput/backend.c | 2 +- backend/multi/backend.c | 2 +- backend/wayland/backend.c | 2 +- backend/wayland/output.c | 2 +- backend/x11/backend.c | 2 +- backend/x11/output.c | 2 +- include/wlr/backend/drm.h | 4 ++-- include/wlr/backend/headless.h | 4 ++-- include/wlr/backend/libinput.h | 2 +- include/wlr/backend/multi.h | 2 +- include/wlr/backend/wayland.h | 4 ++-- include/wlr/backend/x11.h | 4 ++-- include/wlr/render/gles2.h | 6 +++--- include/wlr/render/pixman.h | 4 ++-- include/wlr/render/vulkan.h | 4 ++-- include/xwayland/selection.h | 4 ++-- render/gles2/renderer.c | 4 ++-- render/gles2/texture.c | 2 +- render/pixman/renderer.c | 4 ++-- render/vulkan/renderer.c | 2 +- render/vulkan/texture.c | 2 +- xwayland/selection/incoming.c | 4 ++-- 26 files changed, 38 insertions(+), 38 deletions(-) diff --git a/backend/drm/backend.c b/backend/drm/backend.c index 5a545b192..c2806152b 100644 --- a/backend/drm/backend.c +++ b/backend/drm/backend.c @@ -95,7 +95,7 @@ static const struct wlr_backend_impl backend_impl = { .commit = backend_commit, }; -bool wlr_backend_is_drm(struct wlr_backend *b) { +bool wlr_backend_is_drm(const struct wlr_backend *b) { return b->impl == &backend_impl; } diff --git a/backend/drm/drm.c b/backend/drm/drm.c index fe9dcc341..6c37ab668 100644 --- a/backend/drm/drm.c +++ b/backend/drm/drm.c @@ -1299,7 +1299,7 @@ static const struct wlr_output_impl output_impl = { .get_primary_formats = drm_connector_get_primary_formats, }; -bool wlr_output_is_drm(struct wlr_output *output) { +bool wlr_output_is_drm(const struct wlr_output *output) { return output->impl == &output_impl; } diff --git a/backend/headless/backend.c b/backend/headless/backend.c index d03f520b8..0f21b53f4 100644 --- a/backend/headless/backend.c +++ b/backend/headless/backend.c @@ -81,6 +81,6 @@ struct wlr_backend *wlr_headless_backend_create(struct wl_event_loop *loop) { return &backend->backend; } -bool wlr_backend_is_headless(struct wlr_backend *backend) { +bool wlr_backend_is_headless(const struct wlr_backend *backend) { return backend->impl == &backend_impl; } diff --git a/backend/headless/output.c b/backend/headless/output.c index a4cdb17c3..0464e0d1b 100644 --- a/backend/headless/output.c +++ b/backend/headless/output.c @@ -106,7 +106,7 @@ static const struct wlr_output_impl output_impl = { .move_cursor = output_move_cursor, }; -bool wlr_output_is_headless(struct wlr_output *wlr_output) { +bool wlr_output_is_headless(const struct wlr_output *wlr_output) { return wlr_output->impl == &output_impl; } diff --git a/backend/libinput/backend.c b/backend/libinput/backend.c index 121731b31..0c72c6849 100644 --- a/backend/libinput/backend.c +++ b/backend/libinput/backend.c @@ -155,7 +155,7 @@ static const struct wlr_backend_impl backend_impl = { .destroy = backend_destroy, }; -bool wlr_backend_is_libinput(struct wlr_backend *b) { +bool wlr_backend_is_libinput(const struct wlr_backend *b) { return b->impl == &backend_impl; } diff --git a/backend/multi/backend.c b/backend/multi/backend.c index 3d8fb96f5..3a9aca7e6 100644 --- a/backend/multi/backend.c +++ b/backend/multi/backend.c @@ -173,7 +173,7 @@ struct wlr_backend *wlr_multi_backend_create(struct wl_event_loop *loop) { return &backend->backend; } -bool wlr_backend_is_multi(struct wlr_backend *b) { +bool wlr_backend_is_multi(const struct wlr_backend *b) { return b->impl == &backend_impl; } diff --git a/backend/wayland/backend.c b/backend/wayland/backend.c index 14a783b67..0d294b543 100644 --- a/backend/wayland/backend.c +++ b/backend/wayland/backend.c @@ -577,7 +577,7 @@ static const struct wlr_backend_impl backend_impl = { .get_drm_fd = backend_get_drm_fd, }; -bool wlr_backend_is_wl(struct wlr_backend *b) { +bool wlr_backend_is_wl(const struct wlr_backend *b) { return b->impl == &backend_impl; } diff --git a/backend/wayland/output.c b/backend/wayland/output.c index fb4d1f914..c3442f411 100644 --- a/backend/wayland/output.c +++ b/backend/wayland/output.c @@ -1027,7 +1027,7 @@ static const struct wlr_output_impl output_impl = { .get_primary_formats = output_get_formats, }; -bool wlr_output_is_wl(struct wlr_output *wlr_output) { +bool wlr_output_is_wl(const struct wlr_output *wlr_output) { return wlr_output->impl == &output_impl; } diff --git a/backend/x11/backend.c b/backend/x11/backend.c index 0983bad0a..20118016d 100644 --- a/backend/x11/backend.c +++ b/backend/x11/backend.c @@ -218,7 +218,7 @@ static const struct wlr_backend_impl backend_impl = { .get_drm_fd = backend_get_drm_fd, }; -bool wlr_backend_is_x11(struct wlr_backend *backend) { +bool wlr_backend_is_x11(const struct wlr_backend *backend) { return backend->impl == &backend_impl; } diff --git a/backend/x11/output.c b/backend/x11/output.c index 51a9c1441..40e601f0b 100644 --- a/backend/x11/output.c +++ b/backend/x11/output.c @@ -711,7 +711,7 @@ void handle_x11_configure_notify(struct wlr_x11_output *output, wlr_output_state_finish(&state); } -bool wlr_output_is_x11(struct wlr_output *wlr_output) { +bool wlr_output_is_x11(const struct wlr_output *wlr_output) { return wlr_output->impl == &output_impl; } diff --git a/include/wlr/backend/drm.h b/include/wlr/backend/drm.h index 3ca6390ab..233bbcb74 100644 --- a/include/wlr/backend/drm.h +++ b/include/wlr/backend/drm.h @@ -39,8 +39,8 @@ struct wlr_drm_lease { struct wlr_backend *wlr_drm_backend_create(struct wlr_session *session, struct wlr_device *dev, struct wlr_backend *parent); -bool wlr_backend_is_drm(struct wlr_backend *backend); -bool wlr_output_is_drm(struct wlr_output *output); +bool wlr_backend_is_drm(const struct wlr_backend *backend); +bool wlr_output_is_drm(const struct wlr_output *output); /** * Get the parent DRM backend, if any. diff --git a/include/wlr/backend/headless.h b/include/wlr/backend/headless.h index 126f1ff21..5487c6085 100644 --- a/include/wlr/backend/headless.h +++ b/include/wlr/backend/headless.h @@ -25,7 +25,7 @@ struct wlr_backend *wlr_headless_backend_create(struct wl_event_loop *loop); struct wlr_output *wlr_headless_add_output(struct wlr_backend *backend, unsigned int width, unsigned int height); -bool wlr_backend_is_headless(struct wlr_backend *backend); -bool wlr_output_is_headless(struct wlr_output *output); +bool wlr_backend_is_headless(const struct wlr_backend *backend); +bool wlr_output_is_headless(const struct wlr_output *output); #endif diff --git a/include/wlr/backend/libinput.h b/include/wlr/backend/libinput.h index 2a4e1996e..cb5ef824d 100644 --- a/include/wlr/backend/libinput.h +++ b/include/wlr/backend/libinput.h @@ -29,7 +29,7 @@ struct libinput_device *wlr_libinput_get_device_handle( struct libinput_tablet_tool *wlr_libinput_get_tablet_tool_handle( struct wlr_tablet_tool *wlr_tablet_tool); -bool wlr_backend_is_libinput(struct wlr_backend *backend); +bool wlr_backend_is_libinput(const struct wlr_backend *backend); bool wlr_input_device_is_libinput(struct wlr_input_device *device); #endif diff --git a/include/wlr/backend/multi.h b/include/wlr/backend/multi.h index c4322d98b..cc3e41b4e 100644 --- a/include/wlr/backend/multi.h +++ b/include/wlr/backend/multi.h @@ -26,7 +26,7 @@ bool wlr_multi_backend_add(struct wlr_backend *multi, void wlr_multi_backend_remove(struct wlr_backend *multi, struct wlr_backend *backend); -bool wlr_backend_is_multi(struct wlr_backend *backend); +bool wlr_backend_is_multi(const struct wlr_backend *backend); bool wlr_multi_is_empty(struct wlr_backend *backend); void wlr_multi_for_each_backend(struct wlr_backend *backend, diff --git a/include/wlr/backend/wayland.h b/include/wlr/backend/wayland.h index c732de68c..7a41b3d09 100644 --- a/include/wlr/backend/wayland.h +++ b/include/wlr/backend/wayland.h @@ -46,7 +46,7 @@ struct wlr_output *wlr_wl_output_create_from_surface(struct wlr_backend *backend /** * Check whether the provided backend is a Wayland backend. */ -bool wlr_backend_is_wl(struct wlr_backend *backend); +bool wlr_backend_is_wl(const struct wlr_backend *backend); /** * Check whether the provided input device is a Wayland input device. @@ -56,7 +56,7 @@ bool wlr_input_device_is_wl(struct wlr_input_device *device); /** * Check whether the provided output device is a Wayland output device. */ -bool wlr_output_is_wl(struct wlr_output *output); +bool wlr_output_is_wl(const struct wlr_output *output); /** * Sets the title of a struct wlr_output which is a Wayland toplevel. diff --git a/include/wlr/backend/x11.h b/include/wlr/backend/x11.h index 1791041ce..8e8bc9870 100644 --- a/include/wlr/backend/x11.h +++ b/include/wlr/backend/x11.h @@ -31,7 +31,7 @@ struct wlr_output *wlr_x11_output_create(struct wlr_backend *backend); /** * Check whether this backend is an X11 backend. */ -bool wlr_backend_is_x11(struct wlr_backend *backend); +bool wlr_backend_is_x11(const struct wlr_backend *backend); /** * Check whether this input device is an X11 input device. @@ -41,7 +41,7 @@ bool wlr_input_device_is_x11(struct wlr_input_device *device); /** * Check whether this output device is an X11 output device. */ -bool wlr_output_is_x11(struct wlr_output *output); +bool wlr_output_is_x11(const struct wlr_output *output); /** * Sets the title of a struct wlr_output which is an X11 window. diff --git a/include/wlr/render/gles2.h b/include/wlr/render/gles2.h index 454e7eb0e..87a57fa54 100644 --- a/include/wlr/render/gles2.h +++ b/include/wlr/render/gles2.h @@ -42,9 +42,9 @@ struct wlr_gles2_texture_attribs { bool has_alpha; }; -bool wlr_renderer_is_gles2(struct wlr_renderer *wlr_renderer); -bool wlr_render_timer_is_gles2(struct wlr_render_timer *timer); -bool wlr_texture_is_gles2(struct wlr_texture *texture); +bool wlr_renderer_is_gles2(const struct wlr_renderer *wlr_renderer); +bool wlr_render_timer_is_gles2(const struct wlr_render_timer *timer); +bool wlr_texture_is_gles2(const struct wlr_texture *texture); void wlr_gles2_texture_get_attribs(struct wlr_texture *texture, struct wlr_gles2_texture_attribs *attribs); diff --git a/include/wlr/render/pixman.h b/include/wlr/render/pixman.h index 206dd78c6..2951f5e9e 100644 --- a/include/wlr/render/pixman.h +++ b/include/wlr/render/pixman.h @@ -14,8 +14,8 @@ struct wlr_renderer *wlr_pixman_renderer_create(void); -bool wlr_renderer_is_pixman(struct wlr_renderer *wlr_renderer); -bool wlr_texture_is_pixman(struct wlr_texture *texture); +bool wlr_renderer_is_pixman(const struct wlr_renderer *wlr_renderer); +bool wlr_texture_is_pixman(const struct wlr_texture *texture); pixman_image_t *wlr_pixman_renderer_get_buffer_image( struct wlr_renderer *wlr_renderer, struct wlr_buffer *wlr_buffer); diff --git a/include/wlr/render/vulkan.h b/include/wlr/render/vulkan.h index 50f8c558d..170665dd0 100644 --- a/include/wlr/render/vulkan.h +++ b/include/wlr/render/vulkan.h @@ -25,8 +25,8 @@ VkPhysicalDevice wlr_vk_renderer_get_physical_device(struct wlr_renderer *render VkDevice wlr_vk_renderer_get_device(struct wlr_renderer *renderer); uint32_t wlr_vk_renderer_get_queue_family(struct wlr_renderer *renderer); -bool wlr_renderer_is_vk(struct wlr_renderer *wlr_renderer); -bool wlr_texture_is_vk(struct wlr_texture *texture); +bool wlr_renderer_is_vk(const struct wlr_renderer *wlr_renderer); +bool wlr_texture_is_vk(const struct wlr_texture *texture); void wlr_vk_texture_get_image_attribs(struct wlr_texture *texture, struct wlr_vk_image_attribs *attribs); diff --git a/include/xwayland/selection.h b/include/xwayland/selection.h index 308310232..2f333f8e0 100644 --- a/include/xwayland/selection.h +++ b/include/xwayland/selection.h @@ -82,9 +82,9 @@ void xwm_handle_selection_notify(struct wlr_xwm *xwm, xcb_selection_notify_event_t *event); int xwm_handle_xfixes_selection_notify(struct wlr_xwm *xwm, xcb_xfixes_selection_notify_event_t *event); -bool data_source_is_xwayland(struct wlr_data_source *wlr_source); +bool data_source_is_xwayland(const struct wlr_data_source *wlr_source); bool primary_selection_source_is_xwayland( - struct wlr_primary_selection_source *wlr_source); + const struct wlr_primary_selection_source *wlr_source); void xwm_seat_handle_start_drag(struct wlr_xwm *xwm, struct wlr_drag *drag); diff --git a/render/gles2/renderer.c b/render/gles2/renderer.c index e362daee8..7f01b8acd 100644 --- a/render/gles2/renderer.c +++ b/render/gles2/renderer.c @@ -29,7 +29,7 @@ static const struct wlr_renderer_impl renderer_impl; static const struct wlr_render_timer_impl render_timer_impl; -bool wlr_renderer_is_gles2(struct wlr_renderer *wlr_renderer) { +bool wlr_renderer_is_gles2(const struct wlr_renderer *wlr_renderer) { return wlr_renderer->impl == &renderer_impl; } @@ -40,7 +40,7 @@ struct wlr_gles2_renderer *gles2_get_renderer( return renderer; } -bool wlr_render_timer_is_gles2(struct wlr_render_timer *timer) { +bool wlr_render_timer_is_gles2(const struct wlr_render_timer *timer) { return timer->impl == &render_timer_impl; } diff --git a/render/gles2/texture.c b/render/gles2/texture.c index 9a967ebdb..7460755cd 100644 --- a/render/gles2/texture.c +++ b/render/gles2/texture.c @@ -16,7 +16,7 @@ static const struct wlr_texture_impl texture_impl; -bool wlr_texture_is_gles2(struct wlr_texture *wlr_texture) { +bool wlr_texture_is_gles2(const struct wlr_texture *wlr_texture) { return wlr_texture->impl == &texture_impl; } diff --git a/render/pixman/renderer.c b/render/pixman/renderer.c index 4631a33ab..25c558a9d 100644 --- a/render/pixman/renderer.c +++ b/render/pixman/renderer.c @@ -12,7 +12,7 @@ static const struct wlr_renderer_impl renderer_impl; -bool wlr_renderer_is_pixman(struct wlr_renderer *wlr_renderer) { +bool wlr_renderer_is_pixman(const struct wlr_renderer *wlr_renderer) { return wlr_renderer->impl == &renderer_impl; } @@ -69,7 +69,7 @@ static struct wlr_pixman_buffer *get_buffer( static const struct wlr_texture_impl texture_impl; -bool wlr_texture_is_pixman(struct wlr_texture *texture) { +bool wlr_texture_is_pixman(const struct wlr_texture *texture) { return texture->impl == &texture_impl; } diff --git a/render/vulkan/renderer.c b/render/vulkan/renderer.c index da17a4703..68c5e96eb 100644 --- a/render/vulkan/renderer.c +++ b/render/vulkan/renderer.c @@ -46,7 +46,7 @@ static bool default_debug = true; static const struct wlr_renderer_impl renderer_impl; -bool wlr_renderer_is_vk(struct wlr_renderer *wlr_renderer) { +bool wlr_renderer_is_vk(const struct wlr_renderer *wlr_renderer) { return wlr_renderer->impl == &renderer_impl; } diff --git a/render/vulkan/texture.c b/render/vulkan/texture.c index 57db97a56..452d787da 100644 --- a/render/vulkan/texture.c +++ b/render/vulkan/texture.c @@ -15,7 +15,7 @@ static const struct wlr_texture_impl texture_impl; -bool wlr_texture_is_vk(struct wlr_texture *wlr_texture) { +bool wlr_texture_is_vk(const struct wlr_texture *wlr_texture) { return wlr_texture->impl == &texture_impl; } diff --git a/xwayland/selection/incoming.c b/xwayland/selection/incoming.c index 85d7775cd..4fa851b2f 100644 --- a/xwayland/selection/incoming.c +++ b/xwayland/selection/incoming.c @@ -249,7 +249,7 @@ struct x11_data_source { static const struct wlr_data_source_impl data_source_impl; bool data_source_is_xwayland( - struct wlr_data_source *wlr_source) { + const struct wlr_data_source *wlr_source) { return wlr_source->impl == &data_source_impl; } @@ -292,7 +292,7 @@ static const struct wlr_primary_selection_source_impl primary_selection_source_impl; bool primary_selection_source_is_xwayland( - struct wlr_primary_selection_source *wlr_source) { + const struct wlr_primary_selection_source *wlr_source) { return wlr_source->impl == &primary_selection_source_impl; } From 67ce318b1fb4a5973e80b1ea721fbb23f210d665 Mon Sep 17 00:00:00 2001 From: Simon Zeni Date: Fri, 6 Mar 2026 09:37:31 -0500 Subject: [PATCH 274/311] ci: update dalligi upstream repo --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e02463a9c..3b02ef16f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,4 +1,4 @@ -include: https://git.sr.ht/~emersion/dalligi/blob/master/templates/multi.yml +include: https://gitlab.freedesktop.org/emersion/dalligi/-/raw/master/templates/multi.yml alpine: extends: .dalligi pages: true From 3dafaa4df313f607730a8f95ac37f822b8f94101 Mon Sep 17 00:00:00 2001 From: hrdl Date: Thu, 19 Feb 2026 20:18:16 +0000 Subject: [PATCH 275/311] render/vulkan: relax minimum Vulkan API version to 1.0 This allows using the vulkan renderer on platforms that provide all the necessary Vulkan extensions. Tested on a Mali G52 platform with Mesa 26.0.0 and 25.3.5, which only support Vulkan API 1.0. --- include/render/vulkan.h | 4 ++++ render/vulkan/renderer.c | 6 ++--- render/vulkan/texture.c | 4 ++-- render/vulkan/vulkan.c | 50 ++++++++++++++++++---------------------- 4 files changed, 32 insertions(+), 32 deletions(-) diff --git a/include/render/vulkan.h b/include/render/vulkan.h index f96619f2c..c5d571ef7 100644 --- a/include/render/vulkan.h +++ b/include/render/vulkan.h @@ -56,6 +56,10 @@ struct wlr_vk_device { PFN_vkGetSemaphoreFdKHR vkGetSemaphoreFdKHR; PFN_vkImportSemaphoreFdKHR vkImportSemaphoreFdKHR; PFN_vkQueueSubmit2KHR vkQueueSubmit2KHR; + PFN_vkBindImageMemory2KHR vkBindImageMemory2KHR; + PFN_vkCreateSamplerYcbcrConversionKHR vkCreateSamplerYcbcrConversionKHR; + PFN_vkDestroySamplerYcbcrConversionKHR vkDestroySamplerYcbcrConversionKHR; + PFN_vkGetImageMemoryRequirements2KHR vkGetImageMemoryRequirements2KHR; } api; uint32_t format_prop_count; diff --git a/render/vulkan/renderer.c b/render/vulkan/renderer.c index 68c5e96eb..52c87e9e2 100644 --- a/render/vulkan/renderer.c +++ b/render/vulkan/renderer.c @@ -1188,7 +1188,7 @@ static void vulkan_destroy(struct wlr_renderer *wlr_renderer) { vkDestroyPipelineLayout(dev->dev, pipeline_layout->vk, NULL); vkDestroyDescriptorSetLayout(dev->dev, pipeline_layout->ds, NULL); vkDestroySampler(dev->dev, pipeline_layout->sampler, NULL); - vkDestroySamplerYcbcrConversion(dev->dev, pipeline_layout->ycbcr.conversion, NULL); + renderer->dev->api.vkDestroySamplerYcbcrConversionKHR(dev->dev, pipeline_layout->ycbcr.conversion, NULL); free(pipeline_layout); } @@ -2049,10 +2049,10 @@ struct wlr_vk_pipeline_layout *get_or_create_pipeline_layout( .yChromaOffset = VK_CHROMA_LOCATION_MIDPOINT, .chromaFilter = VK_FILTER_LINEAR, }; - res = vkCreateSamplerYcbcrConversion(renderer->dev->dev, + res = renderer->dev->api.vkCreateSamplerYcbcrConversionKHR(renderer->dev->dev, &conversion_create_info, NULL, &pipeline_layout->ycbcr.conversion); if (res != VK_SUCCESS) { - wlr_vk_error("vkCreateSamplerYcbcrConversion", res); + wlr_vk_error("vkCreateSamplerYcbcrConversionKHR", res); free(pipeline_layout); return NULL; } diff --git a/render/vulkan/texture.c b/render/vulkan/texture.c index 452d787da..d769a0b9e 100644 --- a/render/vulkan/texture.c +++ b/render/vulkan/texture.c @@ -653,7 +653,7 @@ VkImage vulkan_import_dmabuf(struct wlr_vk_renderer *renderer, .sType = VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2, }; - vkGetImageMemoryRequirements2(dev, &memri, &memr); + renderer->dev->api.vkGetImageMemoryRequirements2KHR(dev, &memri, &memr); int mem = vulkan_find_mem_type(renderer->dev, 0, memr.memoryRequirements.memoryTypeBits & fdp.memoryTypeBits); if (mem < 0) { @@ -712,7 +712,7 @@ VkImage vulkan_import_dmabuf(struct wlr_vk_renderer *renderer, } } - res = vkBindImageMemory2(dev, mem_count, bindi); + res = renderer->dev->api.vkBindImageMemory2KHR(dev, mem_count, bindi); if (res != VK_SUCCESS) { wlr_vk_error("vkBindMemory failed", res); goto error_image; diff --git a/render/vulkan/vulkan.c b/render/vulkan/vulkan.c index ee7adc011..d1a0d77d3 100644 --- a/render/vulkan/vulkan.c +++ b/render/vulkan/vulkan.c @@ -81,21 +81,6 @@ static VKAPI_ATTR VkBool32 debug_callback(VkDebugUtilsMessageSeverityFlagBitsEXT } struct wlr_vk_instance *vulkan_instance_create(bool debug) { - // we require vulkan 1.1 - PFN_vkEnumerateInstanceVersion pfEnumInstanceVersion = - (PFN_vkEnumerateInstanceVersion) - vkGetInstanceProcAddr(VK_NULL_HANDLE, "vkEnumerateInstanceVersion"); - if (!pfEnumInstanceVersion) { - wlr_log(WLR_ERROR, "wlroots requires vulkan 1.1 which is not available"); - return NULL; - } - - uint32_t ini_version; - if (pfEnumInstanceVersion(&ini_version) != VK_SUCCESS || - ini_version < VK_API_VERSION_1_1) { - wlr_log(WLR_ERROR, "wlroots requires vulkan 1.1 which is not available"); - return NULL; - } uint32_t avail_extc = 0; VkResult res; @@ -125,7 +110,18 @@ struct wlr_vk_instance *vulkan_instance_create(bool debug) { } size_t extensions_len = 0; - const char *extensions[1] = {0}; + const char *extensions[8] = {0}; + extensions[extensions_len++] = VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME; + extensions[extensions_len++] = VK_KHR_EXTERNAL_SEMAPHORE_CAPABILITIES_EXTENSION_NAME; + extensions[extensions_len++] = VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME; + + for (size_t i = 0; i < extensions_len; i++) { + if (!check_extension(avail_ext_props, avail_extc, extensions[i])) { + wlr_log(WLR_ERROR, "vulkan: required instance extension %s not found", + extensions[i]); + goto error; + } + } bool debug_utils_found = false; if (debug && check_extension(avail_ext_props, avail_extc, @@ -140,7 +136,7 @@ struct wlr_vk_instance *vulkan_instance_create(bool debug) { .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, .pEngineName = "wlroots", .engineVersion = WLR_VERSION_NUM, - .apiVersion = VK_API_VERSION_1_1, + .apiVersion = VK_API_VERSION_1_0, }; VkInstanceCreateInfo instance_info = { @@ -282,20 +278,10 @@ VkPhysicalDevice vulkan_find_drm_phdev(struct wlr_vk_instance *ini, int drm_fd) for (uint32_t i = 0; i < num_phdevs; ++i) { VkPhysicalDevice phdev = phdevs[i]; - // check whether device supports vulkan 1.1, needed for - // vkGetPhysicalDeviceProperties2 VkPhysicalDeviceProperties phdev_props; vkGetPhysicalDeviceProperties(phdev, &phdev_props); - log_phdev(&phdev_props); - if (phdev_props.apiVersion < VK_API_VERSION_1_1) { - // NOTE: we could additionally check whether the - // VkPhysicalDeviceProperties2KHR extension is supported but - // implementations not supporting 1.1 are unlikely in future - continue; - } - // check for extensions uint32_t avail_extc = 0; res = vkEnumerateDeviceExtensionProperties(phdev, NULL, @@ -474,6 +460,12 @@ struct wlr_vk_device *vulkan_device_create(struct wlr_vk_instance *ini, extensions[extensions_len++] = VK_EXT_IMAGE_DRM_FORMAT_MODIFIER_EXTENSION_NAME; extensions[extensions_len++] = VK_KHR_TIMELINE_SEMAPHORE_EXTENSION_NAME; // or vulkan 1.2 extensions[extensions_len++] = VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME; // or vulkan 1.3 + extensions[extensions_len++] = VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME; // or vulkan 1.1 + extensions[extensions_len++] = VK_KHR_BIND_MEMORY_2_EXTENSION_NAME; // or vulkan 1.1 + extensions[extensions_len++] = VK_KHR_SAMPLER_YCBCR_CONVERSION_EXTENSION_NAME; // or vulkan 1.1 + extensions[extensions_len++] = VK_KHR_EXTERNAL_SEMAPHORE_EXTENSION_NAME; // or vulkan 1.1 + extensions[extensions_len++] = VK_KHR_MAINTENANCE_1_EXTENSION_NAME; // or vulkan 1.1 + extensions[extensions_len++] = VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME; // or vulkan 1.1 for (size_t i = 0; i < extensions_len; i++) { if (!check_extension(avail_ext_props, avail_extc, extensions[i])) { @@ -630,6 +622,10 @@ struct wlr_vk_device *vulkan_device_create(struct wlr_vk_instance *ini, load_device_proc(dev, "vkGetSemaphoreCounterValueKHR", &dev->api.vkGetSemaphoreCounterValueKHR); load_device_proc(dev, "vkQueueSubmit2KHR", &dev->api.vkQueueSubmit2KHR); + load_device_proc(dev, "vkBindImageMemory2KHR", &dev->api.vkBindImageMemory2KHR); + load_device_proc(dev, "vkCreateSamplerYcbcrConversionKHR", &dev->api.vkCreateSamplerYcbcrConversionKHR); + load_device_proc(dev, "vkDestroySamplerYcbcrConversionKHR", &dev->api.vkDestroySamplerYcbcrConversionKHR); + load_device_proc(dev, "vkGetImageMemoryRequirements2KHR", &dev->api.vkGetImageMemoryRequirements2KHR); if (has_external_semaphore_fd) { load_device_proc(dev, "vkGetSemaphoreFdKHR", &dev->api.vkGetSemaphoreFdKHR); From 9a931d9ffa1f8771c5dc957e2c224192589633b0 Mon Sep 17 00:00:00 2001 From: Christopher Snowhill Date: Fri, 6 Mar 2026 18:44:26 -0800 Subject: [PATCH 276/311] scene: fix color format compare bool doesn't really support negative values. Fixes: 7cb3393e7 (scene: send color_management_v1 surface feedback) --- types/scene/surface.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/types/scene/surface.c b/types/scene/surface.c index bce8c74a6..7b4d7fd36 100644 --- a/types/scene/surface.c +++ b/types/scene/surface.c @@ -38,7 +38,7 @@ static struct wlr_output *get_surface_frame_pacing_output(struct wlr_surface *su return frame_pacing_output; } -static bool get_tf_preference(enum wlr_color_transfer_function tf) { +static int get_tf_preference(enum wlr_color_transfer_function tf) { switch (tf) { case WLR_COLOR_TRANSFER_FUNCTION_GAMMA22: return 0; @@ -52,7 +52,7 @@ static bool get_tf_preference(enum wlr_color_transfer_function tf) { abort(); // unreachable } -static bool get_primaries_preference(enum wlr_color_named_primaries primaries) { +static int get_primaries_preference(enum wlr_color_named_primaries primaries) { switch (primaries) { case WLR_COLOR_NAMED_PRIMARIES_SRGB: return 0; From 285cee5f3a0001db775009abccfee177f560e1a6 Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Thu, 26 Feb 2026 16:24:16 +0100 Subject: [PATCH 277/311] util/box: Use integer min/max for intersection wlr_box_intersection only operates on integers, so we shouldn't use fmin/fmax. Do the usual and add a local integer min/max helper. --- util/box.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/util/box.c b/util/box.c index aae09888f..489c5ad2e 100644 --- a/util/box.c +++ b/util/box.c @@ -4,6 +4,14 @@ #include #include +static int max(int a, int b) { + return a > b ? a : b; +} + +static int min(int a, int b) { + return a < b ? a : b; +} + void wlr_box_closest_point(const struct wlr_box *box, double x, double y, double *dest_x, double *dest_y) { // if box is empty, then it contains no points, so no closest point either @@ -56,10 +64,10 @@ bool wlr_box_intersection(struct wlr_box *dest, const struct wlr_box *box_a, return false; } - int x1 = fmax(box_a->x, box_b->x); - int y1 = fmax(box_a->y, box_b->y); - int x2 = fmin(box_a->x + box_a->width, box_b->x + box_b->width); - int y2 = fmin(box_a->y + box_a->height, box_b->y + box_b->height); + int x1 = max(box_a->x, box_b->x); + int y1 = max(box_a->y, box_b->y); + int x2 = min(box_a->x + box_a->width, box_b->x + box_b->width); + int y2 = min(box_a->y + box_a->height, box_b->y + box_b->height); dest->x = x1; dest->y = y1; From ff7d09380005e38240e847a359f3a932995cf8b3 Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Thu, 26 Feb 2026 16:41:48 +0100 Subject: [PATCH 278/311] util/box: Add wlr_box_intersects wlr_box_intersection generates a new box based on the intersection of two boxes. Often we simply want to know *if* two boxes intersected, which we can answer much cheaper. Add wlr_box_intersects, in similar vein as wlr_box_contains_box but returning true for any overlap. --- include/wlr/util/box.h | 7 +++++++ util/box.c | 9 +++++++++ 2 files changed, 16 insertions(+) diff --git a/include/wlr/util/box.h b/include/wlr/util/box.h index 64dcbc5cf..f6809e0c3 100644 --- a/include/wlr/util/box.h +++ b/include/wlr/util/box.h @@ -107,6 +107,13 @@ void wlr_fbox_transform(struct wlr_fbox *dest, const struct wlr_fbox *box, #ifdef WLR_USE_UNSTABLE +/** + * Checks whether two boxes intersect. + * + * Returns false if either box is empty. + */ +bool wlr_box_intersects(const struct wlr_box *a, const struct wlr_box *b); + /** * Returns true if the two boxes are equal, false otherwise. */ diff --git a/util/box.c b/util/box.c index 489c5ad2e..62d405488 100644 --- a/util/box.c +++ b/util/box.c @@ -102,6 +102,15 @@ bool wlr_box_contains_box(const struct wlr_box *bigger, const struct wlr_box *sm smaller->y + smaller->height <= bigger->y + bigger->height; } +bool wlr_box_intersects(const struct wlr_box *a, const struct wlr_box *b) { + if (wlr_box_empty(a) || wlr_box_empty(b)) { + return false; + } + + return a->x < b->x + b->width && b->x < a->x + a->width && + a->y < b->y + b->height && b->y < a->y + a->height; +} + void wlr_box_transform(struct wlr_box *dest, const struct wlr_box *box, enum wl_output_transform transform, int width, int height) { struct wlr_box src = {0}; From 648790f43a6ac92edbc77761d071d4e5df66b6d6 Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Thu, 26 Feb 2026 17:12:41 +0100 Subject: [PATCH 279/311] tests: Initial test and benchmark setup Add a unit test for wlr_box and benchmark for wlr_scene_node_at as our first test examples, lowering the barrier for adding more tests as suitable. --- meson.build | 4 ++ meson.options | 1 + test/bench_scene.c | 154 +++++++++++++++++++++++++++++++++++++++++++++ test/meson.build | 10 +++ test/test_box.c | 149 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 318 insertions(+) create mode 100644 test/bench_scene.c create mode 100644 test/meson.build create mode 100644 test/test_box.c diff --git a/meson.build b/meson.build index d9e215afd..27aae8e9f 100644 --- a/meson.build +++ b/meson.build @@ -178,6 +178,10 @@ if get_option('examples') subdir('tinywl') endif +if get_option('tests') + subdir('test') +endif + pkgconfig = import('pkgconfig') pkgconfig.generate( lib_wlr, diff --git a/meson.options b/meson.options index 5863764aa..d8a8ca940 100644 --- a/meson.options +++ b/meson.options @@ -7,5 +7,6 @@ option('backends', type: 'array', choices: ['auto', 'drm', 'libinput', 'x11'], v option('allocators', type: 'array', choices: ['auto', 'gbm', 'udmabuf'], value: ['auto'], description: 'Select built-in allocators') option('session', type: 'feature', value: 'auto', description: 'Enable session support') +option('tests', type: 'boolean', value: true, description: 'Build tests and benchmarks') option('color-management', type: 'feature', value: 'auto', description: 'Enable support for color management') option('libliftoff', type: 'feature', value: 'auto', description: 'Enable support for libliftoff') diff --git a/test/bench_scene.c b/test/bench_scene.c new file mode 100644 index 000000000..6d6b270f0 --- /dev/null +++ b/test/bench_scene.c @@ -0,0 +1,154 @@ +#include +#include +#include + +struct tree_spec { + // Parameters for the tree we'll construct + int depth; + int branching; + int rect_size; + int spread; + + // Stats around the tree we built + int tree_count; + int rect_count; + int max_x; + int max_y; +}; + +static int max(int a, int b) { + return a > b ? a : b; +} + +static double timespec_diff_msec(struct timespec *start, struct timespec *end) { + return (double)(end->tv_sec - start->tv_sec) * 1e3 + + (double)(end->tv_nsec - start->tv_nsec) / 1e6; +} + +static bool build_tree(struct wlr_scene_tree *parent, struct tree_spec *spec, + int depth, int x, int y) { + + if (depth == spec->depth) { + float color[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; + struct wlr_scene_rect *rect = + wlr_scene_rect_create(parent, spec->rect_size, spec->rect_size, color); + if (rect == NULL) { + fprintf(stderr, "wlr_scene_rect_create failed\n"); + return false; + } + wlr_scene_node_set_position(&rect->node, x, y); + spec->max_x = max(spec->max_x, x + spec->rect_size); + spec->max_y = max(spec->max_y, y + spec->rect_size); + spec->rect_count++; + return true; + } + + for (int i = 0; i < spec->branching; i++) { + struct wlr_scene_tree *child = wlr_scene_tree_create(parent); + if (child == NULL) { + fprintf(stderr, "wlr_scene_tree_create failed\n"); + return false; + } + spec->tree_count++; + int offset = i * spec->spread; + wlr_scene_node_set_position(&child->node, offset, offset); + if (!build_tree(child, spec, depth + 1, x + offset, y + offset)) { + return false; + } + } + return true; +} + +static bool bench_create_tree(struct wlr_scene *scene, struct tree_spec *spec) { + struct timespec start, end; + clock_gettime(CLOCK_MONOTONIC, &start); + if (!build_tree(&scene->tree, spec, 0, 0, 0)) { + fprintf(stderr, "build_tree failed\n"); + return false; + } + clock_gettime(CLOCK_MONOTONIC, &end); + + printf("Built tree with %d tree nodes, %d rect nodes\n\n", + spec->tree_count, spec->rect_count); + + double elapsed = timespec_diff_msec(&start, &end); + int nodes = spec->tree_count + spec->rect_count; + printf("create test tree: %d nodes, %.3f ms, %.0f nodes/ms\n", + nodes, elapsed, nodes / elapsed); + return true; +} + +static void bench_scene_node_at(struct wlr_scene *scene, struct tree_spec *spec) { + struct timespec start, end; + int iters = 10000; + int hits = 0; + + clock_gettime(CLOCK_MONOTONIC, &start); + for (int i = 0; i < iters; i++) { + // Spread lookups across the tree extent + double lx = (double)(i * 97 % spec->max_x); + double ly = (double)(i * 53 % spec->max_y); + double nx, ny; + struct wlr_scene_node *node = + wlr_scene_node_at(&scene->tree.node, lx, ly, &nx, &ny); + if (node != NULL) { + hits++; + } + } + clock_gettime(CLOCK_MONOTONIC, &end); + + double elapsed = timespec_diff_msec(&start, &end); + int nodes = (spec->tree_count + spec->rect_count) * iters; + printf("wlr_scene_node_at: %d iters, %.3f ms, %.0f nodes/ms (hits: %d/%d)\n", + iters, elapsed, nodes / elapsed, hits, iters); +} + +static void noop_iterator(struct wlr_scene_buffer *buffer, + int sx, int sy, void *user_data) { + (void)buffer; + (void)sx; + (void)sy; + int *cnt = user_data; + (*cnt)++; +} + +static void bench_scene_node_for_each_buffer(struct wlr_scene *scene, struct tree_spec *spec) { + struct timespec start, end; + int iters = 10000; + int hits = 0; + + clock_gettime(CLOCK_MONOTONIC, &start); + for (int i = 0; i < iters; i++) { + wlr_scene_node_for_each_buffer(&scene->tree.node, + noop_iterator, &hits); + } + clock_gettime(CLOCK_MONOTONIC, &end); + + double elapsed = timespec_diff_msec(&start, &end); + int nodes = (spec->tree_count + spec->rect_count) * iters; + printf("wlr_scene_node_for_each_buffer: %d iters, %.3f ms, %.0f nodes/ms (hits: %d/%d)\n", + iters, elapsed, nodes / elapsed, hits, iters); +} + +int main(void) { + struct wlr_scene *scene = wlr_scene_create(); + if (scene == NULL) { + fprintf(stderr, "wlr_scene_create failed\n"); + return 99; + } + + struct tree_spec spec = { + .depth = 5, + .branching = 5, + .rect_size = 10, + .spread = 100, + }; + if (!bench_create_tree(scene, &spec)) { + return 99; + } + bench_scene_node_at(scene, &spec); + bench_scene_node_for_each_buffer(scene, &spec); + + wlr_scene_node_destroy(&scene->tree.node); + return 0; +} diff --git a/test/meson.build b/test/meson.build new file mode 100644 index 000000000..f51b2c02c --- /dev/null +++ b/test/meson.build @@ -0,0 +1,10 @@ +test( + 'box', + executable('test-box', 'test_box.c', dependencies: wlroots), +) + +benchmark( + 'scene', + executable('bench-scene', 'bench_scene.c', dependencies: wlroots), + timeout: 30, +) diff --git a/test/test_box.c b/test/test_box.c new file mode 100644 index 000000000..fcd95a8e2 --- /dev/null +++ b/test/test_box.c @@ -0,0 +1,149 @@ +#include +#include +#include + +static void test_box_empty(void) { + // NULL is empty + assert(wlr_box_empty(NULL)); + + // Zero width/height + struct wlr_box box = { .x = 0, .y = 0, .width = 0, .height = 10 }; + assert(wlr_box_empty(&box)); + box = (struct wlr_box){ .x = 0, .y = 0, .width = 10, .height = 0 }; + assert(wlr_box_empty(&box)); + + // Negative width/height + box = (struct wlr_box){ .x = 0, .y = 0, .width = -1, .height = 10 }; + assert(wlr_box_empty(&box)); + box = (struct wlr_box){ .x = 0, .y = 0, .width = 10, .height = -1 }; + assert(wlr_box_empty(&box)); + + // Valid box + box = (struct wlr_box){ .x = 0, .y = 0, .width = 10, .height = 10 }; + assert(!wlr_box_empty(&box)); +} + +static void test_box_intersection(void) { + struct wlr_box dest; + + // Overlapping + struct wlr_box a = { .x = 0, .y = 0, .width = 100, .height = 100 }; + struct wlr_box b = { .x = 50, .y = 50, .width = 100, .height = 100 }; + assert(wlr_box_intersection(&dest, &a, &b)); + assert(dest.x == 50 && dest.y == 50 && + dest.width == 50 && dest.height == 50); + + // Non-overlapping + b = (struct wlr_box){ .x = 200, .y = 200, .width = 50, .height = 50 }; + assert(!wlr_box_intersection(&dest, &a, &b)); + assert(dest.width == 0 && dest.height == 0); + + // Touching edges + b = (struct wlr_box){ .x = 100, .y = 0, .width = 50, .height = 50 }; + assert(!wlr_box_intersection(&dest, &a, &b)); + + // Self-intersection + assert(wlr_box_intersection(&dest, &a, &a)); + assert(dest.x == a.x && dest.y == a.y && + dest.width == a.width && dest.height == a.height); + + // Empty input + struct wlr_box empty = { .x = 0, .y = 0, .width = 0, .height = 0 }; + assert(!wlr_box_intersection(&dest, &a, &empty)); + + // NULL input + assert(!wlr_box_intersection(&dest, &a, NULL)); + assert(!wlr_box_intersection(&dest, NULL, &a)); +} + +static void test_box_intersects_box(void) { + // Overlapping + struct wlr_box a = { .x = 0, .y = 0, .width = 100, .height = 100 }; + struct wlr_box b = { .x = 50, .y = 50, .width = 100, .height = 100 }; + assert(wlr_box_intersects(&a, &b)); + + // Non-overlapping + b = (struct wlr_box){ .x = 200, .y = 200, .width = 50, .height = 50 }; + assert(!wlr_box_intersects(&a, &b)); + + // Touching edges + b = (struct wlr_box){ .x = 100, .y = 0, .width = 50, .height = 50 }; + assert(!wlr_box_intersects(&a, &b)); + + // Self-intersection + assert(wlr_box_intersects(&a, &a)); + + // Empty input + struct wlr_box empty = { .x = 0, .y = 0, .width = 0, .height = 0 }; + assert(!wlr_box_intersects(&a, &empty)); + + // NULL input + assert(!wlr_box_intersects(&a, NULL)); + assert(!wlr_box_intersects(NULL, &a)); +} + +static void test_box_contains_point(void) { + struct wlr_box box = { .x = 10, .y = 20, .width = 100, .height = 50 }; + + // Interior point + assert(wlr_box_contains_point(&box, 50, 40)); + + // Inclusive lower bound + assert(wlr_box_contains_point(&box, 10, 20)); + + // Exclusive upper bound + assert(!wlr_box_contains_point(&box, 110, 70)); + assert(!wlr_box_contains_point(&box, 110, 40)); + assert(!wlr_box_contains_point(&box, 50, 70)); + + // Outside + assert(!wlr_box_contains_point(&box, 5, 40)); + assert(!wlr_box_contains_point(&box, 50, 15)); + + // Empty box + struct wlr_box empty = { .x = 0, .y = 0, .width = 0, .height = 0 }; + assert(!wlr_box_contains_point(&empty, 0, 0)); + + // NULL + assert(!wlr_box_contains_point(NULL, 0, 0)); +} + +static void test_box_contains_box(void) { + struct wlr_box outer = { .x = 0, .y = 0, .width = 100, .height = 100 }; + + // Fully contained + struct wlr_box inner = { .x = 10, .y = 10, .width = 50, .height = 50 }; + assert(wlr_box_contains_box(&outer, &inner)); + + // Self-containment + assert(wlr_box_contains_box(&outer, &outer)); + + // Partial overlap — not contained + struct wlr_box partial = { .x = 50, .y = 50, .width = 100, .height = 100 }; + assert(!wlr_box_contains_box(&outer, &partial)); + + // Empty inner + struct wlr_box empty = { .x = 0, .y = 0, .width = 0, .height = 0 }; + assert(!wlr_box_contains_box(&outer, &empty)); + + // Empty outer + assert(!wlr_box_contains_box(&empty, &inner)); + + // NULL + assert(!wlr_box_contains_box(&outer, NULL)); + assert(!wlr_box_contains_box(NULL, &outer)); +} + +int main(void) { +#ifdef NDEBUG + fprintf(stderr, "NDEBUG must be disabled for tests\n"); + return 1; +#endif + + test_box_empty(); + test_box_intersection(); + test_box_intersects_box(); + test_box_contains_point(); + test_box_contains_box(); + return 0; +} From 2938c10cd32f8d28fd74f4e81849513edf15d0cd Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Fri, 27 Feb 2026 13:24:45 +0100 Subject: [PATCH 280/311] ci: Run tests and benchmarks --- .builds/alpine.yml | 3 +++ .builds/archlinux.yml | 4 ++++ .builds/freebsd.yml | 3 +++ 3 files changed, 10 insertions(+) diff --git a/.builds/alpine.yml b/.builds/alpine.yml index affd85411..f8d75d8b1 100644 --- a/.builds/alpine.yml +++ b/.builds/alpine.yml @@ -35,6 +35,9 @@ tasks: cd wlroots ninja -C build sudo ninja -C build install + - test: | + cd wlroots + meson test -C build --verbose - build-features-disabled: | cd wlroots meson setup build --reconfigure -Dauto_features=disabled diff --git a/.builds/archlinux.yml b/.builds/archlinux.yml index fae04ab31..84e04eda7 100644 --- a/.builds/archlinux.yml +++ b/.builds/archlinux.yml @@ -37,6 +37,10 @@ tasks: - clang: | cd wlroots/build-clang ninja + - test: | + cd wlroots/build-gcc + meson test --verbose + meson test --benchmark --verbose - smoke-test: | cd wlroots/build-gcc/tinywl sudo modprobe vkms diff --git a/.builds/freebsd.yml b/.builds/freebsd.yml index a99e9c911..3bfe37489 100644 --- a/.builds/freebsd.yml +++ b/.builds/freebsd.yml @@ -32,6 +32,9 @@ tasks: meson setup build --fatal-meson-warnings -Dauto_features=enabled -Dallocators=gbm ninja -C build sudo ninja -C build install + - test: | + cd wlroots + meson test -C build --verbose - tinywl: | cd wlroots/tinywl make From 7ccef7d9eb621fc3f50fddce2f8775cedf5bcb31 Mon Sep 17 00:00:00 2001 From: Kenny Levinsen Date: Thu, 26 Feb 2026 16:50:16 +0100 Subject: [PATCH 281/311] Adopt wlr_box_intersects where useful This makes wlr_scene_node_at roughly 50% faster, and gives a minor boost to node modification as well. Before: create test tree: 7030 nodes, 473.510 s, 15 nodes/ms wlr_scene_node_at: 10000 iters, 894.945 s, 78552 nodes/ms (hits: 10/10000) wlr_scene_node_for_each_buffer: 10000 iters, 330.597 s, 212646 nodes/ms (hits: 0/10000) After: create test tree: 7030 nodes, 385.930 s, 18 nodes/ms wlr_scene_node_at: 10000 iters, 586.013 s, 119963 nodes/ms (hits: 10/10000) wlr_scene_node_for_each_buffer: 10000 iters, 334.559 s, 210127 nodes/ms (hits: 0/10000) --- types/output/cursor.c | 3 +-- types/output/output.c | 2 +- types/scene/wlr_scene.c | 5 ++--- types/wlr_output_layout.c | 6 ++---- 4 files changed, 6 insertions(+), 10 deletions(-) diff --git a/types/output/cursor.c b/types/output/cursor.c index 5e93b0b2e..4a823dab1 100644 --- a/types/output/cursor.c +++ b/types/output/cursor.c @@ -159,9 +159,8 @@ static void output_cursor_update_visible(struct wlr_output_cursor *cursor) { struct wlr_box cursor_box; output_cursor_get_box(cursor, &cursor_box); - struct wlr_box intersection; cursor->visible = - wlr_box_intersection(&intersection, &output_box, &cursor_box); + wlr_box_intersects(&output_box, &cursor_box); } static bool output_pick_cursor_format(struct wlr_output *output, diff --git a/types/output/output.c b/types/output/output.c index 3715950ce..b1da61ed1 100644 --- a/types/output/output.c +++ b/types/output/output.c @@ -615,7 +615,7 @@ static bool output_basic_test(struct wlr_output *output, }; struct wlr_box dst_box; output_state_get_buffer_dst_box(state, &dst_box); - if (!wlr_box_intersection(&output_box, &output_box, &dst_box)) { + if (!wlr_box_intersects(&output_box, &dst_box)) { wlr_log(WLR_ERROR, "Primary buffer is entirely off-screen or 0-sized"); return false; } diff --git a/types/scene/wlr_scene.c b/types/scene/wlr_scene.c index 51373e15a..2ca93c8cd 100644 --- a/types/scene/wlr_scene.c +++ b/types/scene/wlr_scene.c @@ -238,7 +238,7 @@ static bool _scene_nodes_in_box(struct wlr_scene_node *node, struct wlr_box *box struct wlr_box node_box = { .x = lx, .y = ly }; scene_node_get_size(node, &node_box.width, &node_box.height); - if (wlr_box_intersection(&node_box, &node_box, box) && + if (wlr_box_intersects(&node_box, box) && iterator(node, lx, ly, user_data)) { return true; } @@ -2673,8 +2673,7 @@ static void scene_output_for_each_scene_buffer(const struct wlr_box *output_box, struct wlr_box node_box = { .x = lx, .y = ly }; scene_node_get_size(node, &node_box.width, &node_box.height); - struct wlr_box intersection; - if (wlr_box_intersection(&intersection, output_box, &node_box)) { + if (wlr_box_intersects(output_box, &node_box)) { struct wlr_scene_buffer *scene_buffer = wlr_scene_buffer_from_node(node); user_iterator(scene_buffer, lx, ly, user_data); diff --git a/types/wlr_output_layout.c b/types/wlr_output_layout.c index ef2751179..7226809e7 100644 --- a/types/wlr_output_layout.c +++ b/types/wlr_output_layout.c @@ -260,14 +260,12 @@ bool wlr_output_layout_contains_point(struct wlr_output_layout *layout, bool wlr_output_layout_intersects(struct wlr_output_layout *layout, struct wlr_output *reference, const struct wlr_box *target_lbox) { - struct wlr_box out_box; - if (reference == NULL) { struct wlr_output_layout_output *l_output; wl_list_for_each(l_output, &layout->outputs, link) { struct wlr_box output_box; output_layout_output_get_box(l_output, &output_box); - if (wlr_box_intersection(&out_box, &output_box, target_lbox)) { + if (wlr_box_intersects(&output_box, target_lbox)) { return true; } } @@ -281,7 +279,7 @@ bool wlr_output_layout_intersects(struct wlr_output_layout *layout, struct wlr_box output_box; output_layout_output_get_box(l_output, &output_box); - return wlr_box_intersection(&out_box, &output_box, target_lbox); + return wlr_box_intersects(&output_box, target_lbox); } } From 3c8d199ec13ef247c230a950e0ad3ab1b160ed59 Mon Sep 17 00:00:00 2001 From: Jonathan Marler Date: Tue, 10 Mar 2026 22:44:56 -0600 Subject: [PATCH 282/311] backend/x11: ignore DestroyNotify events The X11 backend subscribes to StructureNotify events, so when output_destroy() calls xcb_destroy_window() the server sends a DestroyNotify back. This is expected and harmless but was logged as an unhandled event. Silence it the same way MAP_NOTIFY and UNMAP_NOTIFY are already silenced. --- backend/x11/backend.c | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/x11/backend.c b/backend/x11/backend.c index 20118016d..de22faa0e 100644 --- a/backend/x11/backend.c +++ b/backend/x11/backend.c @@ -115,6 +115,7 @@ static void handle_x11_event(struct wlr_x11_backend *x11, handle_x11_error(x11, ev); break; } + case XCB_DESTROY_NOTIFY: case XCB_UNMAP_NOTIFY: case XCB_MAP_NOTIFY: break; From 736c0f3f25d813ce68ed0efc99f4068a7250866a Mon Sep 17 00:00:00 2001 From: Diego Viola Date: Mon, 9 Mar 2026 00:39:50 -0300 Subject: [PATCH 283/311] wlr-export-dmabuf-unstable-v1: fix typo --- protocol/wlr-export-dmabuf-unstable-v1.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocol/wlr-export-dmabuf-unstable-v1.xml b/protocol/wlr-export-dmabuf-unstable-v1.xml index 80ea012f5..d41efbb1d 100644 --- a/protocol/wlr-export-dmabuf-unstable-v1.xml +++ b/protocol/wlr-export-dmabuf-unstable-v1.xml @@ -192,7 +192,7 @@ - Unreferences the frame. This request must be called as soon as its no + Unreferences the frame. This request must be called as soon as it's no longer used. It can be called at any time by the client. The client will still have From 39e918edc89306661b4f96393b557e25523adc40 Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Fri, 13 Mar 2026 17:43:52 +0100 Subject: [PATCH 284/311] scene: avoid redundant wl_surface.enter/leave events Currently we send wl_surface.enter/leave when a surface is hidden and shown again on the same output. In practice, this happens very often since compositors like river and sway enable and disable the scene nodes of surfaces as part of their atomic transaction strategy involving rendering saved buffers while waiting for clients to submit new buffers of the desired size. The new strategy documented in the new comments avoids sending redundant events in this case. --- include/wlr/types/wlr_scene.h | 2 -- types/scene/surface.c | 58 +++++++++++++++++++---------------- 2 files changed, 31 insertions(+), 29 deletions(-) diff --git a/include/wlr/types/wlr_scene.h b/include/wlr/types/wlr_scene.h index 46635f4bf..962b01bcc 100644 --- a/include/wlr/types/wlr_scene.h +++ b/include/wlr/types/wlr_scene.h @@ -131,8 +131,6 @@ struct wlr_scene_surface { struct wlr_addon addon; struct wl_listener outputs_update; - struct wl_listener output_enter; - struct wl_listener output_leave; struct wl_listener output_sample; struct wl_listener frame_done; struct wl_listener surface_destroy; diff --git a/types/scene/surface.c b/types/scene/surface.c index 7b4d7fd36..c7a53a1b3 100644 --- a/types/scene/surface.c +++ b/types/scene/surface.c @@ -94,14 +94,44 @@ static void handle_scene_buffer_outputs_update( struct wl_listener *listener, void *data) { struct wlr_scene_surface *surface = wl_container_of(listener, surface, outputs_update); + struct wlr_scene_outputs_update_event *event = data; struct wlr_scene *scene = scene_node_get_root(&surface->buffer->node); // If the surface is no longer visible on any output, keep the last sent // preferred configuration to avoid unnecessary redraws - if (wl_list_empty(&surface->surface->current_outputs)) { + if (event->size == 0) { return; } + // To avoid sending redundant leave/enter events when a surface is hidden and then shown + // without moving to a different output the following policy is implemented: + // + // 1. When a surface transitions from being visible on >0 outputs to being visible on 0 outputs + // don't send any leave events. + // + // 2. When a surface transitions from being visible on 0 outputs to being visible on >0 outputs + // send leave events for all entered outputs on which the surface is no longer visible as + // well as enter events for any outputs not already entered. + struct wlr_surface_output *entered_output; + wl_list_for_each(entered_output, &surface->surface->current_outputs, link) { + bool active = false; + for (size_t i = 0; i < event->size; i++) { + if (entered_output->output == event->active[i]->output) { + active = true; + break; + } + } + if (!active) { + wlr_surface_send_leave(surface->surface, entered_output->output); + } + } + + for (size_t i = 0; i < event->size; i++) { + // This function internally checks if an enter event was already sent for the output + // to avoid sending redundant events. + wlr_surface_send_enter(surface->surface, event->active[i]->output); + } + double scale = get_surface_preferred_buffer_scale(surface->surface); wlr_fractional_scale_v1_notify_scale(surface->surface, scale); wlr_surface_set_preferred_buffer_scale(surface->surface, ceil(scale)); @@ -114,24 +144,6 @@ static void handle_scene_buffer_outputs_update( } } -static void handle_scene_buffer_output_enter( - struct wl_listener *listener, void *data) { - struct wlr_scene_surface *surface = - wl_container_of(listener, surface, output_enter); - struct wlr_scene_output *output = data; - - wlr_surface_send_enter(surface->surface, output->output); -} - -static void handle_scene_buffer_output_leave( - struct wl_listener *listener, void *data) { - struct wlr_scene_surface *surface = - wl_container_of(listener, surface, output_leave); - struct wlr_scene_output *output = data; - - wlr_surface_send_leave(surface->surface, output->output); -} - static void handle_scene_buffer_output_sample( struct wl_listener *listener, void *data) { struct wlr_scene_surface *surface = @@ -380,8 +392,6 @@ static void surface_addon_destroy(struct wlr_addon *addon) { wlr_addon_finish(&surface->addon); wl_list_remove(&surface->outputs_update.link); - wl_list_remove(&surface->output_enter.link); - wl_list_remove(&surface->output_leave.link); wl_list_remove(&surface->output_sample.link); wl_list_remove(&surface->frame_done.link); wl_list_remove(&surface->surface_destroy.link); @@ -427,12 +437,6 @@ struct wlr_scene_surface *wlr_scene_surface_create(struct wlr_scene_tree *parent surface->outputs_update.notify = handle_scene_buffer_outputs_update; wl_signal_add(&scene_buffer->events.outputs_update, &surface->outputs_update); - surface->output_enter.notify = handle_scene_buffer_output_enter; - wl_signal_add(&scene_buffer->events.output_enter, &surface->output_enter); - - surface->output_leave.notify = handle_scene_buffer_output_leave; - wl_signal_add(&scene_buffer->events.output_leave, &surface->output_leave); - surface->output_sample.notify = handle_scene_buffer_output_sample; wl_signal_add(&scene_buffer->events.output_sample, &surface->output_sample); From 7f87c7fe90fe1a392b6a14c74700e72b4315c752 Mon Sep 17 00:00:00 2001 From: Alexander Orzechowski Date: Fri, 13 Mar 2026 13:19:06 -0400 Subject: [PATCH 285/311] wlr_scene: Nuke buffer output_enter / output_leave outputs_update should be used instead. --- include/wlr/types/wlr_scene.h | 2 -- types/scene/wlr_scene.c | 37 ++++------------------------------- 2 files changed, 4 insertions(+), 35 deletions(-) diff --git a/include/wlr/types/wlr_scene.h b/include/wlr/types/wlr_scene.h index 962b01bcc..523d6f8e5 100644 --- a/include/wlr/types/wlr_scene.h +++ b/include/wlr/types/wlr_scene.h @@ -169,8 +169,6 @@ struct wlr_scene_buffer { struct { struct wl_signal outputs_update; // struct wlr_scene_outputs_update_event - struct wl_signal output_enter; // struct wlr_scene_output - struct wl_signal output_leave; // struct wlr_scene_output struct wl_signal output_sample; // struct wlr_scene_output_sample_event struct wl_signal frame_done; // struct wlr_scene_frame_done_event } events; diff --git a/types/scene/wlr_scene.c b/types/scene/wlr_scene.c index 2ca93c8cd..cc772368d 100644 --- a/types/scene/wlr_scene.c +++ b/types/scene/wlr_scene.c @@ -113,24 +113,11 @@ void wlr_scene_node_destroy(struct wlr_scene_node *node) { if (node->type == WLR_SCENE_NODE_BUFFER) { struct wlr_scene_buffer *scene_buffer = wlr_scene_buffer_from_node(node); - uint64_t active = scene_buffer->active_outputs; - if (active) { - struct wlr_scene_output *scene_output; - wl_list_for_each(scene_output, &scene->outputs, link) { - if (active & (1ull << scene_output->index)) { - wl_signal_emit_mutable(&scene_buffer->events.output_leave, - scene_output); - } - } - } - scene_buffer_set_buffer(scene_buffer, NULL); scene_buffer_set_texture(scene_buffer, NULL); pixman_region32_fini(&scene_buffer->opaque_region); wlr_drm_syncobj_timeline_unref(scene_buffer->wait_timeline); - assert(wl_list_empty(&scene_buffer->events.output_leave.listener_list)); - assert(wl_list_empty(&scene_buffer->events.output_enter.listener_list)); assert(wl_list_empty(&scene_buffer->events.outputs_update.listener_list)); assert(wl_list_empty(&scene_buffer->events.output_sample.listener_list)); assert(wl_list_empty(&scene_buffer->events.frame_done.listener_list)); @@ -481,28 +468,12 @@ static void update_node_update_outputs(struct wlr_scene_node *node, (struct wlr_linux_dmabuf_feedback_v1_init_options){0}; } - uint64_t old_active = scene_buffer->active_outputs; - scene_buffer->active_outputs = active_outputs; - - struct wlr_scene_output *scene_output; - wl_list_for_each(scene_output, outputs, link) { - uint64_t mask = 1ull << scene_output->index; - bool intersects = active_outputs & mask; - bool intersects_before = old_active & mask; - - if (intersects && !intersects_before) { - wl_signal_emit_mutable(&scene_buffer->events.output_enter, scene_output); - } else if (!intersects && intersects_before) { - wl_signal_emit_mutable(&scene_buffer->events.output_leave, scene_output); - } - } - // if there are active outputs on this node, we should always have a primary // output - assert(!scene_buffer->active_outputs || scene_buffer->primary_output); + assert(!active_outputs || scene_buffer->primary_output); // Skip output update event if nothing was updated - if (old_active == active_outputs && + if (scene_buffer->active_outputs == active_outputs && (!force || ((1ull << force->index) & ~active_outputs)) && old_primary_output == scene_buffer->primary_output) { return; @@ -515,6 +486,7 @@ static void update_node_update_outputs(struct wlr_scene_node *node, }; size_t i = 0; + struct wlr_scene_output *scene_output; wl_list_for_each(scene_output, outputs, link) { if (~active_outputs & (1ull << scene_output->index)) { continue; @@ -524,6 +496,7 @@ static void update_node_update_outputs(struct wlr_scene_node *node, outputs_array[i++] = scene_output; } + scene_buffer->active_outputs = active_outputs; wl_signal_emit_mutable(&scene_buffer->events.outputs_update, &event); } @@ -869,8 +842,6 @@ struct wlr_scene_buffer *wlr_scene_buffer_create(struct wlr_scene_tree *parent, scene_node_init(&scene_buffer->node, WLR_SCENE_NODE_BUFFER, parent); wl_signal_init(&scene_buffer->events.outputs_update); - wl_signal_init(&scene_buffer->events.output_enter); - wl_signal_init(&scene_buffer->events.output_leave); wl_signal_init(&scene_buffer->events.output_sample); wl_signal_init(&scene_buffer->events.frame_done); From 3cb2cf9425a35333661b1edcc0d34e92ea109fd3 Mon Sep 17 00:00:00 2001 From: llyyr Date: Fri, 13 Mar 2026 23:32:06 +0530 Subject: [PATCH 286/311] scene: use wl_list_for_each_safe to iterate outputs The outputs loop in handle_scene_buffer_outputs_update may remove entries from the list while iterating, so use wl_list_for_each_safe instead of wl_list_for_each. Fixes: 39e918edc893 ("scene: avoid redundant wl_surface.enter/leave events") --- types/scene/surface.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/types/scene/surface.c b/types/scene/surface.c index c7a53a1b3..7aa75c6d4 100644 --- a/types/scene/surface.c +++ b/types/scene/surface.c @@ -112,8 +112,8 @@ static void handle_scene_buffer_outputs_update( // 2. When a surface transitions from being visible on 0 outputs to being visible on >0 outputs // send leave events for all entered outputs on which the surface is no longer visible as // well as enter events for any outputs not already entered. - struct wlr_surface_output *entered_output; - wl_list_for_each(entered_output, &surface->surface->current_outputs, link) { + struct wlr_surface_output *entered_output, *tmp; + wl_list_for_each_safe(entered_output, tmp, &surface->surface->current_outputs, link) { bool active = false; for (size_t i = 0; i < event->size; i++) { if (entered_output->output == event->active[i]->output) { From 1fc928d5280b77fdc19238225a64ad0585f8b723 Mon Sep 17 00:00:00 2001 From: Scott Moreau Date: Thu, 12 Mar 2026 17:34:41 -0600 Subject: [PATCH 287/311] wlr_ext_image_copy_capture_v1: Fix crash when client creates a cursor session not implemented server side This guards against a crash where the server implements wlr_ext_image_capture_source_v1_interface without setting .get_pointer_cursor(). In general, we should install a NULL check here because this is a crash waiting to happen. Now, instead of crashing, the resource will be created and the copy capture session will be stopped. --- types/wlr_ext_image_copy_capture_v1.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/types/wlr_ext_image_copy_capture_v1.c b/types/wlr_ext_image_copy_capture_v1.c index c9e7b0079..2e969b4a9 100644 --- a/types/wlr_ext_image_copy_capture_v1.c +++ b/types/wlr_ext_image_copy_capture_v1.c @@ -518,14 +518,16 @@ static void cursor_session_handle_get_capture_session(struct wl_client *client, return; } - cursor_session->capture_session_created = true; - + struct wlr_ext_image_copy_capture_manager_v1 *manager = NULL; struct wlr_ext_image_capture_source_v1 *source = NULL; + if (cursor_session != NULL) { + manager = cursor_session->manager; + cursor_session->capture_session_created = true; source = &cursor_session->source->base; } - session_create(cursor_session_resource, new_id, source, 0, cursor_session->manager); + session_create(cursor_session_resource, new_id, source, 0, manager); } static const struct ext_image_copy_capture_cursor_session_v1_interface cursor_session_impl = { From ec746d3e3ee2f1f4a9bd49e800b3ba8b73be72a9 Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Tue, 17 Mar 2026 09:29:21 +0100 Subject: [PATCH 288/311] virtual-keyboard: add wlr_virtual_keyboard_v1_from_resource() I want to use the zwp_virtual_keyboard_v1 object in a custom river protocol and need to be able to obtain the corresponding wlroots struct. --- include/wlr/types/wlr_virtual_keyboard_v1.h | 11 +++++++++++ types/wlr_virtual_keyboard_v1.c | 10 +++++----- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/include/wlr/types/wlr_virtual_keyboard_v1.h b/include/wlr/types/wlr_virtual_keyboard_v1.h index babc535a5..a5fc589cb 100644 --- a/include/wlr/types/wlr_virtual_keyboard_v1.h +++ b/include/wlr/types/wlr_virtual_keyboard_v1.h @@ -38,7 +38,18 @@ struct wlr_virtual_keyboard_v1 { struct wlr_virtual_keyboard_manager_v1* wlr_virtual_keyboard_manager_v1_create( struct wl_display *display); +/** + * Get the struct wlr_virtual_keyboard_v1 corresponding to a zwp_virtual_keyboard_v1 resource. + * + * Asserts that the resource is a valid zwp_virtual_keyboard_v1 resource created by wlroots. + * + * Returns NULL if the resource is inert. + */ +struct wlr_virtual_keyboard_v1 *wlr_virtual_keyboard_v1_from_resource( + struct wl_resource *resource); + struct wlr_virtual_keyboard_v1 *wlr_input_device_get_virtual_keyboard( struct wlr_input_device *wlr_dev); + #endif diff --git a/types/wlr_virtual_keyboard_v1.c b/types/wlr_virtual_keyboard_v1.c index e7dcb3ec3..2bc703fb4 100644 --- a/types/wlr_virtual_keyboard_v1.c +++ b/types/wlr_virtual_keyboard_v1.c @@ -15,7 +15,7 @@ static const struct wlr_keyboard_impl keyboard_impl = { static const struct zwp_virtual_keyboard_v1_interface virtual_keyboard_impl; -static struct wlr_virtual_keyboard_v1 *virtual_keyboard_from_resource( +struct wlr_virtual_keyboard_v1 *wlr_virtual_keyboard_v1_from_resource( struct wl_resource *resource) { assert(wl_resource_instance_of(resource, &zwp_virtual_keyboard_v1_interface, &virtual_keyboard_impl)); @@ -39,7 +39,7 @@ static void virtual_keyboard_keymap(struct wl_client *client, struct wl_resource *resource, uint32_t format, int32_t fd, uint32_t size) { struct wlr_virtual_keyboard_v1 *keyboard = - virtual_keyboard_from_resource(resource); + wlr_virtual_keyboard_v1_from_resource(resource); if (keyboard == NULL) { return; } @@ -76,7 +76,7 @@ static void virtual_keyboard_key(struct wl_client *client, struct wl_resource *resource, uint32_t time, uint32_t key, uint32_t state) { struct wlr_virtual_keyboard_v1 *keyboard = - virtual_keyboard_from_resource(resource); + wlr_virtual_keyboard_v1_from_resource(resource); if (keyboard == NULL) { return; } @@ -99,7 +99,7 @@ static void virtual_keyboard_modifiers(struct wl_client *client, struct wl_resource *resource, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) { struct wlr_virtual_keyboard_v1 *keyboard = - virtual_keyboard_from_resource(resource); + wlr_virtual_keyboard_v1_from_resource(resource); if (keyboard == NULL) { return; } @@ -115,7 +115,7 @@ static void virtual_keyboard_modifiers(struct wl_client *client, static void virtual_keyboard_destroy_resource(struct wl_resource *resource) { struct wlr_virtual_keyboard_v1 *keyboard = - virtual_keyboard_from_resource(resource); + wlr_virtual_keyboard_v1_from_resource(resource); if (keyboard == NULL) { return; } From 1fa8bb8f7ac0b68aee1fb6754446039f51b1a380 Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Tue, 17 Mar 2026 09:51:50 +0100 Subject: [PATCH 289/311] virtual-keyboard: handle seat destroy We must make the virtual keyboard inert when the seat is destroyed. --- include/wlr/types/wlr_virtual_keyboard_v1.h | 4 ++ types/wlr_virtual_keyboard_v1.c | 41 ++++++++++++++------- 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/include/wlr/types/wlr_virtual_keyboard_v1.h b/include/wlr/types/wlr_virtual_keyboard_v1.h index a5fc589cb..dda1530b1 100644 --- a/include/wlr/types/wlr_virtual_keyboard_v1.h +++ b/include/wlr/types/wlr_virtual_keyboard_v1.h @@ -33,6 +33,10 @@ struct wlr_virtual_keyboard_v1 { bool has_keymap; struct wl_list link; // wlr_virtual_keyboard_manager_v1.virtual_keyboards + + struct { + struct wl_listener seat_destroy; + } WLR_PRIVATE; }; struct wlr_virtual_keyboard_manager_v1* wlr_virtual_keyboard_manager_v1_create( diff --git a/types/wlr_virtual_keyboard_v1.c b/types/wlr_virtual_keyboard_v1.c index 2bc703fb4..011fbfec7 100644 --- a/types/wlr_virtual_keyboard_v1.c +++ b/types/wlr_virtual_keyboard_v1.c @@ -113,21 +113,24 @@ static void virtual_keyboard_modifiers(struct wl_client *client, mods_depressed, mods_latched, mods_locked, group); } -static void virtual_keyboard_destroy_resource(struct wl_resource *resource) { - struct wlr_virtual_keyboard_v1 *keyboard = - wlr_virtual_keyboard_v1_from_resource(resource); - if (keyboard == NULL) { - return; - } - - wlr_keyboard_finish(&keyboard->keyboard); - - wl_resource_set_user_data(keyboard->resource, NULL); - wl_list_remove(&keyboard->link); - free(keyboard); +static void virtual_keyboard_destroy(struct wlr_virtual_keyboard_v1 *virtual_keyboard) { + wlr_keyboard_finish(&virtual_keyboard->keyboard); + wl_resource_set_user_data(virtual_keyboard->resource, NULL); + wl_list_remove(&virtual_keyboard->seat_destroy.link); + wl_list_remove(&virtual_keyboard->link); + free(virtual_keyboard); } -static void virtual_keyboard_destroy(struct wl_client *client, +static void virtual_keyboard_destroy_resource(struct wl_resource *resource) { + struct wlr_virtual_keyboard_v1 *virtual_keyboard = + wlr_virtual_keyboard_v1_from_resource(resource); + if (virtual_keyboard == NULL) { + return; + } + virtual_keyboard_destroy(virtual_keyboard); +} + +static void virtual_keyboard_handle_destroy(struct wl_client *client, struct wl_resource *resource) { wl_resource_destroy(resource); } @@ -136,7 +139,7 @@ static const struct zwp_virtual_keyboard_v1_interface virtual_keyboard_impl = { .keymap = virtual_keyboard_keymap, .key = virtual_keyboard_key, .modifiers = virtual_keyboard_modifiers, - .destroy = virtual_keyboard_destroy, + .destroy = virtual_keyboard_handle_destroy, }; static const struct zwp_virtual_keyboard_manager_v1_interface manager_impl; @@ -148,6 +151,13 @@ static struct wlr_virtual_keyboard_manager_v1 *manager_from_resource( return wl_resource_get_user_data(resource); } +static void virtual_keyboard_handle_seat_destroy(struct wl_listener *listener, + void *data) { + struct wlr_virtual_keyboard_v1 *virtual_keyboard = wl_container_of(listener, virtual_keyboard, + seat_destroy); + virtual_keyboard_destroy(virtual_keyboard); +} + static void virtual_keyboard_manager_create_virtual_keyboard( struct wl_client *client, struct wl_resource *resource, struct wl_resource *seat, uint32_t id) { @@ -181,6 +191,9 @@ static void virtual_keyboard_manager_create_virtual_keyboard( virtual_keyboard->seat = seat_client->seat; wl_resource_set_user_data(keyboard_resource, virtual_keyboard); + wl_signal_add(&seat_client->events.destroy, &virtual_keyboard->seat_destroy); + virtual_keyboard->seat_destroy.notify = virtual_keyboard_handle_seat_destroy; + wl_list_insert(&manager->virtual_keyboards, &virtual_keyboard->link); wl_signal_emit_mutable(&manager->events.new_virtual_keyboard, From 58c158dba6efea063b1120b1bae41ff96487baa0 Mon Sep 17 00:00:00 2001 From: David Turner Date: Tue, 10 Feb 2026 17:11:13 +0000 Subject: [PATCH 290/311] output: Add color-representation to output state Add color_representation to wlr_output_state, holding color representation metadata about the primary buffer. This can be set using wlr_output_state_set_primary_color_representation() and a new enum value WLR_OUTPUT_STATE_COLOR_REPRESENTATION in wlr_output_state.committed indicates when this data is present. Also add color-representation to wlr_output, and discard color-representation in wlr_output_state if it matches what's already been committed to the output. --- include/wlr/types/wlr_output.h | 16 ++++++++++++++++ types/output/output.c | 14 ++++++++++++++ types/output/state.c | 8 ++++++++ 3 files changed, 38 insertions(+) diff --git a/include/wlr/types/wlr_output.h b/include/wlr/types/wlr_output.h index 4485b3694..c8e44b0e6 100644 --- a/include/wlr/types/wlr_output.h +++ b/include/wlr/types/wlr_output.h @@ -77,6 +77,7 @@ enum wlr_output_state_field { WLR_OUTPUT_STATE_SIGNAL_TIMELINE = 1 << 11, WLR_OUTPUT_STATE_COLOR_TRANSFORM = 1 << 12, WLR_OUTPUT_STATE_IMAGE_DESCRIPTION = 1 << 13, + WLR_OUTPUT_STATE_COLOR_REPRESENTATION = 1 << 14, }; enum wlr_output_state_mode_type { @@ -142,6 +143,10 @@ struct wlr_output_state { * regular page-flip at the next wlr_output.frame event. */ bool tearing_page_flip; + // Set if (committed & WLR_OUTPUT_STATE_COLOR_REPRESENTATION) + enum wlr_color_encoding color_encoding; + enum wlr_color_range color_range; + enum wlr_output_state_mode_type mode_type; struct wlr_output_mode *mode; struct { @@ -205,6 +210,8 @@ struct wlr_output { enum wl_output_transform transform; enum wlr_output_adaptive_sync_status adaptive_sync_status; uint32_t render_format; + enum wlr_color_encoding color_encoding; + enum wlr_color_range color_range; const struct wlr_output_image_description *image_description; // Indicates whether making changes to adaptive sync status is supported. @@ -625,6 +632,15 @@ void wlr_output_state_set_color_transform(struct wlr_output_state *state, bool wlr_output_state_set_image_description(struct wlr_output_state *state, const struct wlr_output_image_description *image_desc); +/** + * Set the color encoding and range of the primary scanout buffer. + * + * Pass WLR_COLOR_ENCODING_NONE / WLR_COLOR_RANGE_NONE to reset to defaults. + */ +void wlr_output_state_set_color_encoding_and_range( + struct wlr_output_state *state, + enum wlr_color_encoding encoding, enum wlr_color_range range); + /** * Copies the output state from src to dst. It is safe to then * wlr_output_state_finish() src and have dst still be valid. diff --git a/types/output/output.c b/types/output/output.c index b1da61ed1..46da1f425 100644 --- a/types/output/output.c +++ b/types/output/output.c @@ -233,6 +233,11 @@ static void output_apply_state(struct wlr_output *output, output->transform = state->transform; } + if (state->committed & WLR_OUTPUT_STATE_COLOR_REPRESENTATION) { + output->color_encoding = state->color_encoding; + output->color_range = state->color_range; + } + if (state->committed & WLR_OUTPUT_STATE_IMAGE_DESCRIPTION) { if (state->image_description != NULL) { output->image_description_value = *state->image_description; @@ -580,6 +585,11 @@ static uint32_t output_compare_state(struct wlr_output *output, output->color_transform == state->color_transform) { fields |= WLR_OUTPUT_STATE_COLOR_TRANSFORM; } + if ((state->committed & WLR_OUTPUT_STATE_COLOR_REPRESENTATION) && + output->color_encoding == state->color_encoding && + output->color_range == state->color_range) { + fields |= WLR_OUTPUT_STATE_COLOR_REPRESENTATION; + } return fields; } @@ -632,6 +642,10 @@ static bool output_basic_test(struct wlr_output *output, wlr_log(WLR_DEBUG, "Tried to set signal timeline without a buffer"); return false; } + if (state->committed & WLR_OUTPUT_STATE_COLOR_REPRESENTATION) { + wlr_log(WLR_DEBUG, "Tried to set color representation without a buffer"); + return false; + } } if (state->committed & WLR_OUTPUT_STATE_RENDER_FORMAT) { diff --git a/types/output/state.c b/types/output/state.c index f2c655e21..5f44c18e8 100644 --- a/types/output/state.c +++ b/types/output/state.c @@ -141,6 +141,14 @@ bool wlr_output_state_set_image_description(struct wlr_output_state *state, return true; } +void wlr_output_state_set_color_encoding_and_range( + struct wlr_output_state *state, + enum wlr_color_encoding encoding, enum wlr_color_range range) { + state->committed |= WLR_OUTPUT_STATE_COLOR_REPRESENTATION; + state->color_encoding = encoding; + state->color_range = range; +} + bool wlr_output_state_copy(struct wlr_output_state *dst, const struct wlr_output_state *src) { struct wlr_output_state copy = *src; From 80bcef908b0849615e14827da11bc84eec98f7e3 Mon Sep 17 00:00:00 2001 From: David Turner Date: Tue, 10 Feb 2026 17:11:45 +0000 Subject: [PATCH 291/311] scene: Set color representation on scanout When doing direct-scanout, if the surface has color-representation metadata present then pass on that metadata to the output state. Also, if a buffer has color representation IDENTITY+FULL then normalise this to NONE+NONE which is equivalent. --- types/scene/wlr_scene.c | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/types/scene/wlr_scene.c b/types/scene/wlr_scene.c index cc772368d..362946234 100644 --- a/types/scene/wlr_scene.c +++ b/types/scene/wlr_scene.c @@ -2057,15 +2057,6 @@ static enum scene_direct_scanout_result scene_entry_try_direct_scanout( return SCANOUT_INELIGIBLE; } - bool is_color_repr_none = buffer->color_encoding == WLR_COLOR_ENCODING_NONE && - buffer->color_range == WLR_COLOR_RANGE_NONE; - bool is_color_repr_identity_full = buffer->color_encoding == WLR_COLOR_ENCODING_IDENTITY && - buffer->color_range == WLR_COLOR_RANGE_FULL; - - if (!(is_color_repr_none || is_color_repr_identity_full)) { - return SCANOUT_INELIGIBLE; - } - // We want to ensure optimal buffer selection, but as direct-scanout can be enabled and disabled // on a frame-by-frame basis, we wait for a few frames to send the new format recommendations. // Maybe we should only send feedback in this case if tests fail. @@ -2107,6 +2098,18 @@ static enum scene_direct_scanout_result scene_entry_try_direct_scanout( if (buffer->wait_timeline != NULL) { wlr_output_state_set_wait_timeline(&pending, buffer->wait_timeline, buffer->wait_point); } + + if (buffer->color_encoding == WLR_COLOR_ENCODING_IDENTITY && + buffer->color_range == WLR_COLOR_RANGE_FULL) { + // IDENTITY+FULL (used for RGB formats) is equivalent to no color + // representation being set at all. + wlr_output_state_set_color_encoding_and_range(&pending, + WLR_COLOR_ENCODING_NONE, WLR_COLOR_RANGE_NONE); + } else { + wlr_output_state_set_color_encoding_and_range(&pending, + buffer->color_encoding, buffer->color_range); + } + if (!wlr_output_test_state(scene_output->output, &pending)) { wlr_output_state_finish(&pending); return SCANOUT_CANDIDATE; From dca0703dac7364a3e3cbec8944a69d56ea3d798f Mon Sep 17 00:00:00 2001 From: David Turner Date: Thu, 13 Feb 2025 16:23:00 +0000 Subject: [PATCH 292/311] backend/drm: Add color_range/encoding properties Add the following optional DRM properties, for use by the color-representation-v1 protocol: - COLOR_ENCODING - COLOR_RANGE --- backend/drm/properties.c | 2 ++ include/backend/drm/properties.h | 16 ++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/backend/drm/properties.c b/backend/drm/properties.c index 314023954..4c6bdcb36 100644 --- a/backend/drm/properties.c +++ b/backend/drm/properties.c @@ -50,6 +50,8 @@ static const struct prop_info crtc_info[] = { static const struct prop_info plane_info[] = { #define INDEX(name) (offsetof(struct wlr_drm_plane_props, name) / sizeof(uint32_t)) + { "COLOR_ENCODING", INDEX(color_encoding) }, + { "COLOR_RANGE", INDEX(color_range) }, { "CRTC_H", INDEX(crtc_h) }, { "CRTC_ID", INDEX(crtc_id) }, { "CRTC_W", INDEX(crtc_w) }, diff --git a/include/backend/drm/properties.h b/include/backend/drm/properties.h index c02d655ba..20e0a5bff 100644 --- a/include/backend/drm/properties.h +++ b/include/backend/drm/properties.h @@ -65,6 +65,22 @@ struct wlr_drm_plane_props { uint32_t hotspot_x; uint32_t hotspot_y; uint32_t in_fence_fd; + + uint32_t color_encoding; // Not guaranteed to exist + uint32_t color_range; // Not guaranteed to exist +}; + +// Equivalent to wlr_drm_color_encoding defined in the kernel (but not exported) +enum wlr_drm_color_encoding { + WLR_DRM_COLOR_YCBCR_BT601, + WLR_DRM_COLOR_YCBCR_BT709, + WLR_DRM_COLOR_YCBCR_BT2020, +}; + +// Equivalent to wlr_drm_color_range defined in the kernel (but not exported) +enum wlr_drm_color_range { + WLR_DRM_COLOR_YCBCR_FULL_RANGE, + WLR_DRM_COLOR_YCBCR_LIMITED_RANGE, }; bool get_drm_connector_props(int fd, uint32_t id, From abb6eeb42219e4b73b10227b296d1b299cff03c3 Mon Sep 17 00:00:00 2001 From: David Turner Date: Thu, 13 Feb 2025 17:54:57 +0000 Subject: [PATCH 293/311] backend/drm/atomic: Add support for color representation Basic implementation of color representation in drm/atomic: when buffers are presented for scanout which have color-representation data attached, set the correct color encoding and range on the plane. If the plane does not support color-representation then the commit will fail and the caller can retry without color-representation. --- backend/drm/atomic.c | 61 ++++++++++++++++++++++++++++++++++++++++++++ backend/drm/drm.c | 3 ++- 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/backend/drm/atomic.c b/backend/drm/atomic.c index 41773d4f5..9bccff6ce 100644 --- a/backend/drm/atomic.c +++ b/backend/drm/atomic.c @@ -1,3 +1,4 @@ +#include #include #include #include @@ -484,6 +485,62 @@ static void set_plane_props(struct atomic *atom, struct wlr_drm_backend *drm, atomic_add(atom, id, props->crtc_h, dst_box->height); } +static void set_color_encoding_and_range(struct atomic *atom, + struct wlr_drm_backend *drm, struct wlr_drm_plane *plane, + enum wlr_color_encoding encoding, enum wlr_color_range range) { + uint32_t id = plane->id; + const struct wlr_drm_plane_props *props = &plane->props; + + uint32_t color_encoding; + switch (encoding) { + case WLR_COLOR_ENCODING_NONE: + case WLR_COLOR_ENCODING_BT601: + color_encoding = WLR_DRM_COLOR_YCBCR_BT601; + break; + case WLR_COLOR_ENCODING_BT709: + color_encoding = WLR_DRM_COLOR_YCBCR_BT709; + break; + case WLR_COLOR_ENCODING_BT2020: + color_encoding = WLR_DRM_COLOR_YCBCR_BT2020; + break; + default: + wlr_log(WLR_DEBUG, "Unsupported color encoding %d", encoding); + atom->failed = true; + return; + } + + if (props->color_encoding) { + atomic_add(atom, id, props->color_encoding, color_encoding); + } else { + wlr_log(WLR_DEBUG, "Plane %"PRIu32" is missing the COLOR_ENCODING property", + id); + atom->failed = true; + return; + } + + uint32_t color_range; + switch (range) { + case WLR_COLOR_RANGE_NONE: + case WLR_COLOR_RANGE_LIMITED: + color_range = WLR_DRM_COLOR_YCBCR_LIMITED_RANGE; + break; + case WLR_COLOR_RANGE_FULL: + color_range = WLR_DRM_COLOR_YCBCR_FULL_RANGE; + break; + default: + assert(0); // Unreachable + } + + if (props->color_range) { + atomic_add(atom, id, props->color_range, color_range); + } else { + wlr_log(WLR_DEBUG, "Plane %"PRIu32" is missing the COLOR_RANGE property", + id); + atom->failed = true; + return; + } +} + static bool supports_cursor_hotspots(const struct wlr_drm_plane *plane) { return plane->props.hotspot_x && plane->props.hotspot_y; } @@ -550,6 +607,10 @@ static void atomic_connector_add(struct atomic *atom, set_plane_props(atom, drm, crtc->primary, state->primary_fb, crtc->id, &state->primary_viewport.dst_box, &state->primary_viewport.src_box); + if (state->base->committed & WLR_OUTPUT_STATE_COLOR_REPRESENTATION) { + set_color_encoding_and_range(atom, drm, crtc->primary, + state->base->color_encoding, state->base->color_range); + } if (crtc->primary->props.fb_damage_clips != 0) { atomic_add(atom, crtc->primary->id, crtc->primary->props.fb_damage_clips, state->fb_damage_clips); diff --git a/backend/drm/drm.c b/backend/drm/drm.c index 6c37ab668..9b90022d2 100644 --- a/backend/drm/drm.c +++ b/backend/drm/drm.c @@ -43,7 +43,8 @@ static const uint32_t COMMIT_OUTPUT_STATE = WLR_OUTPUT_STATE_WAIT_TIMELINE | WLR_OUTPUT_STATE_SIGNAL_TIMELINE | WLR_OUTPUT_STATE_COLOR_TRANSFORM | - WLR_OUTPUT_STATE_IMAGE_DESCRIPTION; + WLR_OUTPUT_STATE_IMAGE_DESCRIPTION | + WLR_OUTPUT_STATE_COLOR_REPRESENTATION; static const uint32_t SUPPORTED_OUTPUT_STATE = WLR_OUTPUT_STATE_BACKEND_OPTIONAL | COMMIT_OUTPUT_STATE; From 0af9b9d003bd6b565f30f94755bd9f9a81c0926d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Poisot?= Date: Fri, 31 Jan 2025 18:23:22 +0000 Subject: [PATCH 294/311] render/drm_syncobj: add wlr_drm_syncobj_timeline_signal() --- include/wlr/render/drm_syncobj.h | 4 ++++ render/drm_syncobj.c | 8 ++++++++ types/wlr_linux_drm_syncobj_v1.c | 6 +----- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/include/wlr/render/drm_syncobj.h b/include/wlr/render/drm_syncobj.h index deef72dc9..fabe23d1c 100644 --- a/include/wlr/render/drm_syncobj.h +++ b/include/wlr/render/drm_syncobj.h @@ -90,6 +90,10 @@ bool wlr_drm_syncobj_timeline_transfer(struct wlr_drm_syncobj_timeline *dst, */ bool wlr_drm_syncobj_timeline_check(struct wlr_drm_syncobj_timeline *timeline, uint64_t point, uint32_t flags, bool *result); +/** + * Signals a timeline point. + */ +bool wlr_drm_syncobj_timeline_signal(struct wlr_drm_syncobj_timeline *timeline, uint64_t point); /** * Asynchronously wait for a timeline point. * diff --git a/render/drm_syncobj.c b/render/drm_syncobj.c index e1a407a1e..2abe2cff6 100644 --- a/render/drm_syncobj.c +++ b/render/drm_syncobj.c @@ -177,6 +177,14 @@ bool wlr_drm_syncobj_timeline_check(struct wlr_drm_syncobj_timeline *timeline, return true; } +bool wlr_drm_syncobj_timeline_signal(struct wlr_drm_syncobj_timeline *timeline, uint64_t point) { + if (drmSyncobjTimelineSignal(timeline->drm_fd, &timeline->handle, &point, 1) != 0) { + wlr_log(WLR_ERROR, "drmSyncobjTimelineSignal() failed"); + return false; + } + return true; +} + static int handle_eventfd_ready(int ev_fd, uint32_t mask, void *data) { struct wlr_drm_syncobj_timeline_waiter *waiter = data; diff --git a/types/wlr_linux_drm_syncobj_v1.c b/types/wlr_linux_drm_syncobj_v1.c index 988d44e01..a2e56dcbb 100644 --- a/types/wlr_linux_drm_syncobj_v1.c +++ b/types/wlr_linux_drm_syncobj_v1.c @@ -475,11 +475,7 @@ struct release_signaller { static void release_signaller_handle_buffer_release(struct wl_listener *listener, void *data) { struct release_signaller *signaller = wl_container_of(listener, signaller, buffer_release); - if (drmSyncobjTimelineSignal(signaller->timeline->drm_fd, &signaller->timeline->handle, - &signaller->point, 1) != 0) { - wlr_log(WLR_ERROR, "drmSyncobjTimelineSignal() failed"); - } - + wlr_drm_syncobj_timeline_signal(signaller->timeline, signaller->point); wlr_drm_syncobj_timeline_unref(signaller->timeline); wl_list_remove(&signaller->buffer_release.link); free(signaller); From 1f3d351abb1585454098830ee3a43e4cfef67550 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Poisot?= Date: Fri, 31 Jan 2025 18:36:07 +0000 Subject: [PATCH 295/311] scene: add buffer release point to 'sample' event --- include/wlr/types/wlr_scene.h | 4 ++++ types/scene/wlr_scene.c | 26 ++++++++++++++++++++++++-- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/include/wlr/types/wlr_scene.h b/include/wlr/types/wlr_scene.h index 523d6f8e5..f6f97cfea 100644 --- a/include/wlr/types/wlr_scene.h +++ b/include/wlr/types/wlr_scene.h @@ -153,6 +153,8 @@ struct wlr_scene_outputs_update_event { struct wlr_scene_output_sample_event { struct wlr_scene_output *output; bool direct_scanout; + struct wlr_drm_syncobj_timeline *release_timeline; + uint64_t release_point; }; struct wlr_scene_frame_done_event { @@ -264,6 +266,8 @@ struct wlr_scene_output { struct wlr_drm_syncobj_timeline *in_timeline; uint64_t in_point; + struct wlr_drm_syncobj_timeline *out_timeline; + uint64_t out_point; } WLR_PRIVATE; }; diff --git a/types/scene/wlr_scene.c b/types/scene/wlr_scene.c index 362946234..7231422e8 100644 --- a/types/scene/wlr_scene.c +++ b/types/scene/wlr_scene.c @@ -1524,6 +1524,8 @@ static void scene_entry_render(struct render_list_entry *entry, const struct ren struct wlr_scene_output_sample_event sample_event = { .output = data->output, .direct_scanout = false, + .release_timeline = data->output->in_timeline, + .release_point = data->output->in_point, }; wl_signal_emit_mutable(&scene_buffer->events.output_sample, &sample_event); @@ -1756,7 +1758,10 @@ struct wlr_scene_output *wlr_scene_output_create(struct wlr_scene *scene, if (drm_fd >= 0 && output->backend->features.timeline && output->renderer != NULL && output->renderer->features.timeline) { scene_output->in_timeline = wlr_drm_syncobj_timeline_create(drm_fd); - if (scene_output->in_timeline == NULL) { + scene_output->out_timeline = wlr_drm_syncobj_timeline_create(drm_fd); + if (scene_output->in_timeline == NULL || scene_output->out_timeline == NULL) { + wlr_drm_syncobj_timeline_unref(scene_output->in_timeline); + wlr_drm_syncobj_timeline_unref(scene_output->out_timeline); return NULL; } } @@ -1811,7 +1816,14 @@ void wlr_scene_output_destroy(struct wlr_scene_output *scene_output) { wl_list_remove(&scene_output->output_commit.link); wl_list_remove(&scene_output->output_damage.link); wl_list_remove(&scene_output->output_needs_frame.link); - wlr_drm_syncobj_timeline_unref(scene_output->in_timeline); + if (scene_output->in_timeline != NULL) { + wlr_drm_syncobj_timeline_signal(scene_output->in_timeline, UINT64_MAX); + wlr_drm_syncobj_timeline_unref(scene_output->in_timeline); + } + if (scene_output->out_timeline != NULL) { + wlr_drm_syncobj_timeline_signal(scene_output->out_timeline, UINT64_MAX); + wlr_drm_syncobj_timeline_unref(scene_output->out_timeline); + } wlr_color_transform_unref(scene_output->gamma_lut_color_transform); wlr_color_transform_unref(scene_output->prev_gamma_lut_color_transform); wlr_color_transform_unref(scene_output->prev_supplied_color_transform); @@ -2099,6 +2111,11 @@ static enum scene_direct_scanout_result scene_entry_try_direct_scanout( wlr_output_state_set_wait_timeline(&pending, buffer->wait_timeline, buffer->wait_point); } + if (scene_output->out_timeline) { + scene_output->out_point++; + wlr_output_state_set_signal_timeline(&pending, scene_output->out_timeline, scene_output->out_point); + } + if (buffer->color_encoding == WLR_COLOR_ENCODING_IDENTITY && buffer->color_range == WLR_COLOR_RANGE_FULL) { // IDENTITY+FULL (used for RGB formats) is equivalent to no color @@ -2121,6 +2138,8 @@ static enum scene_direct_scanout_result scene_entry_try_direct_scanout( struct wlr_scene_output_sample_event sample_event = { .output = scene_output, .direct_scanout = true, + .release_timeline = data->output->out_timeline, + .release_point = data->output->out_point, }; wl_signal_emit_mutable(&buffer->events.output_sample, &sample_event); return SCANOUT_SUCCESS; @@ -2580,6 +2599,9 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, if (scene_output->in_timeline != NULL) { wlr_output_state_set_wait_timeline(state, scene_output->in_timeline, scene_output->in_point); + scene_output->out_point++; + wlr_output_state_set_signal_timeline(state, scene_output->out_timeline, + scene_output->out_point); } if (!render_gamma_lut) { From e83a679e2363d27275996cb64fe51d8804bdd7f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Poisot?= Date: Fri, 31 Jan 2025 18:43:23 +0000 Subject: [PATCH 296/311] drm/syncobj: add timeline point merger utility --- include/render/drm_syncobj_merger.h | 44 +++++++++ render/drm_syncobj_merger.c | 133 ++++++++++++++++++++++++++++ render/meson.build | 2 + 3 files changed, 179 insertions(+) create mode 100644 include/render/drm_syncobj_merger.h create mode 100644 render/drm_syncobj_merger.c diff --git a/include/render/drm_syncobj_merger.h b/include/render/drm_syncobj_merger.h new file mode 100644 index 000000000..c55c87314 --- /dev/null +++ b/include/render/drm_syncobj_merger.h @@ -0,0 +1,44 @@ +#ifndef WLR_RENDER_DRM_SYNCOBJ_MERGER_H +#define WLR_RENDER_DRM_SYNCOBJ_MERGER_H + +#include + +/** + * Accumulate timeline points, to have a destination timeline point be + * signalled when all inputs are + */ +struct wlr_drm_syncobj_merger { + int n_ref; + struct wlr_drm_syncobj_timeline *dst_timeline; + uint64_t dst_point; + int sync_fd; +}; + +/** + * Create a new merger. + * + * The given timeline point will be signalled when all input points are + * signalled and the merger is destroyed. + */ +struct wlr_drm_syncobj_merger *wlr_drm_syncobj_merger_create( + struct wlr_drm_syncobj_timeline *dst_timeline, uint64_t dst_point); + +struct wlr_drm_syncobj_merger *wlr_drm_syncobj_merger_ref( + struct wlr_drm_syncobj_merger *merger); + +/** + * Target timeline point is materialized when all inputs are, and the merger is + * destroyed. + */ +void wlr_drm_syncobj_merger_unref(struct wlr_drm_syncobj_merger *merger); +/** + * Add a new timeline point to wait for. + * + * If the point is not materialized, the supplied event loop is used to schedule + * a wait. + */ +bool wlr_drm_syncobj_merger_add(struct wlr_drm_syncobj_merger *merger, + struct wlr_drm_syncobj_timeline *dst_timeline, uint64_t dst_point, + struct wl_event_loop *loop); + +#endif diff --git a/render/drm_syncobj_merger.c b/render/drm_syncobj_merger.c new file mode 100644 index 000000000..d50d28c28 --- /dev/null +++ b/render/drm_syncobj_merger.c @@ -0,0 +1,133 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "render/drm_syncobj_merger.h" + +#include "config.h" + +#if HAVE_LINUX_SYNC_FILE + +#include +#include + +static int sync_file_merge(int fd1, int fd2) { + // The kernel will automatically prune signalled fences + struct sync_merge_data merge_data = { .fd2 = fd2 }; + if (ioctl(fd1, SYNC_IOC_MERGE, &merge_data) < 0) { + wlr_log_errno(WLR_ERROR, "ioctl(SYNC_IOC_MERGE) failed"); + return -1; + } + + return merge_data.fence; +} + +#else + +static int sync_file_merge(int fd1, int fd2) { + wlr_log(WLR_ERROR, "sync_file support is unavailable"); + return -1; +} + +#endif + +struct wlr_drm_syncobj_merger *wlr_drm_syncobj_merger_create( + struct wlr_drm_syncobj_timeline *dst_timeline, uint64_t dst_point) { + struct wlr_drm_syncobj_merger *merger = calloc(1, sizeof(*merger)); + if (merger == NULL) { + return NULL; + } + merger->n_ref = 1; + merger->dst_timeline = wlr_drm_syncobj_timeline_ref(dst_timeline); + merger->dst_point = dst_point; + merger->sync_fd = -1; + return merger; +} + +struct wlr_drm_syncobj_merger *wlr_drm_syncobj_merger_ref( + struct wlr_drm_syncobj_merger *merger) { + assert(merger->n_ref > 0); + merger->n_ref++; + return merger; +} + +void wlr_drm_syncobj_merger_unref(struct wlr_drm_syncobj_merger *merger) { + if (merger == NULL) { + return; + } + assert(merger->n_ref > 0); + merger->n_ref--; + if (merger->n_ref > 0) { + return; + } + + if (merger->sync_fd != -1) { + wlr_drm_syncobj_timeline_import_sync_file(merger->dst_timeline, + merger->dst_point, merger->sync_fd); + close(merger->sync_fd); + } else { + wlr_drm_syncobj_timeline_signal(merger->dst_timeline, merger->dst_point); + } + wlr_drm_syncobj_timeline_unref(merger->dst_timeline); + free(merger); +} + +static bool merger_add_exportable(struct wlr_drm_syncobj_merger *merger, + struct wlr_drm_syncobj_timeline *src_timeline, uint64_t src_point) { + int new_sync = wlr_drm_syncobj_timeline_export_sync_file(src_timeline, src_point); + if (merger->sync_fd != -1) { + int fd2 = new_sync; + new_sync = sync_file_merge(merger->sync_fd, fd2); + close(fd2); + close(merger->sync_fd); + } + merger->sync_fd = new_sync; + return true; +} + +struct export_waiter { + struct wlr_drm_syncobj_timeline_waiter waiter; + struct wlr_drm_syncobj_merger *merger; + struct wlr_drm_syncobj_timeline *src_timeline; + uint64_t src_point; +}; + +static void export_waiter_handle_ready(struct wlr_drm_syncobj_timeline_waiter *waiter) { + struct export_waiter *add = wl_container_of(waiter, add, waiter); + merger_add_exportable(add->merger, add->src_timeline, add->src_point); + wlr_drm_syncobj_merger_unref(add->merger); + wlr_drm_syncobj_timeline_unref(add->src_timeline); + wlr_drm_syncobj_timeline_waiter_finish(&add->waiter); + free(add); +} + +bool wlr_drm_syncobj_merger_add(struct wlr_drm_syncobj_merger *merger, + struct wlr_drm_syncobj_timeline *src_timeline, uint64_t src_point, + struct wl_event_loop *loop) { + assert(loop != NULL); + bool exportable = false; + int flags = DRM_SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE; + if (!wlr_drm_syncobj_timeline_check(src_timeline, src_point, flags, &exportable)) { + return false; + } + if (exportable) { + return merger_add_exportable(merger, src_timeline, src_point); + } + struct export_waiter *add = calloc(1, sizeof(*add)); + if (add == NULL) { + return false; + } + if (!wlr_drm_syncobj_timeline_waiter_init(&add->waiter, src_timeline, src_point, + flags, loop, export_waiter_handle_ready)) { + return false; + } + add->merger = merger; + add->src_timeline = wlr_drm_syncobj_timeline_ref(src_timeline); + add->src_point = src_point; + merger->n_ref++; + return true; +} diff --git a/render/meson.build b/render/meson.build index aaaf2ec48..517d76bf0 100644 --- a/render/meson.build +++ b/render/meson.build @@ -9,6 +9,7 @@ wlr_files += files( 'color.c', 'dmabuf.c', 'drm_format_set.c', + 'drm_syncobj_merger.c', 'drm_syncobj.c', 'pass.c', 'pixel_format.c', @@ -28,6 +29,7 @@ else endif internal_config.set10('HAVE_EVENTFD', cc.has_header('sys/eventfd.h')) +internal_config.set10('HAVE_LINUX_SYNC_FILE', cc.has_header('linux/sync_file.h')) if 'gles2' in renderers or 'auto' in renderers egl = dependency('egl', required: 'gles2' in renderers) From bfd6e619fc8642fb3f2cca6f9cb94d257def8780 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Poisot?= Date: Thu, 12 Mar 2026 23:03:10 +0000 Subject: [PATCH 297/311] linux_drm_syncobj_v1: add release point accumulation This changes the behavior of wlr_linux_drm_syncobj_surface_v1 to automatically signal release of previous commits as they are replaced. Users must call wlr_linux_drm_syncobj_v1_state_add_release_point or wlr_linux_drm_syncobj_v1_state_signal_release_with_buffer to delay the signal as appropriate. --- include/wlr/types/wlr_linux_drm_syncobj_v1.h | 22 +++++++++- types/wlr_linux_drm_syncobj_v1.c | 45 ++++++++++++++++---- 2 files changed, 57 insertions(+), 10 deletions(-) diff --git a/include/wlr/types/wlr_linux_drm_syncobj_v1.h b/include/wlr/types/wlr_linux_drm_syncobj_v1.h index 733350412..70a3f547f 100644 --- a/include/wlr/types/wlr_linux_drm_syncobj_v1.h +++ b/include/wlr/types/wlr_linux_drm_syncobj_v1.h @@ -19,8 +19,11 @@ struct wlr_linux_drm_syncobj_surface_v1_state { struct wlr_drm_syncobj_timeline *acquire_timeline; uint64_t acquire_point; - struct wlr_drm_syncobj_timeline *release_timeline; - uint64_t release_point; + struct { + struct wlr_drm_syncobj_timeline *release_timeline; + uint64_t release_point; + struct wlr_drm_syncobj_merger *release_merger; + } WLR_PRIVATE; }; struct wlr_linux_drm_syncobj_manager_v1 { @@ -55,4 +58,19 @@ struct wlr_linux_drm_syncobj_surface_v1_state *wlr_linux_drm_syncobj_v1_get_surf bool wlr_linux_drm_syncobj_v1_state_signal_release_with_buffer( struct wlr_linux_drm_syncobj_surface_v1_state *state, struct wlr_buffer *buffer); +/** + * Register a release point for buffer usage. + * + * This function may be called multiple times for the same commit. The client's + * release point will be signalled when all registered points are signalled, and + * a new buffer has been committed. + * + * Because the given release point may not be materialized, a wl_event_loop must + * be supplied to schedule a wait internally, if needed + */ +bool wlr_linux_drm_syncobj_v1_state_add_release_point( + struct wlr_linux_drm_syncobj_surface_v1_state *state, + struct wlr_drm_syncobj_timeline *release_timeline, uint64_t release_point, + struct wl_event_loop *event_loop); + #endif diff --git a/types/wlr_linux_drm_syncobj_v1.c b/types/wlr_linux_drm_syncobj_v1.c index a2e56dcbb..200f4c9e6 100644 --- a/types/wlr_linux_drm_syncobj_v1.c +++ b/types/wlr_linux_drm_syncobj_v1.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -11,6 +12,7 @@ #include #include "config.h" #include "linux-drm-syncobj-v1-protocol.h" +#include "render/drm_syncobj_merger.h" #define LINUX_DRM_SYNCOBJ_V1_VERSION 1 @@ -158,20 +160,38 @@ static void surface_synced_finish_state(void *_state) { struct wlr_linux_drm_syncobj_surface_v1_state *state = _state; wlr_drm_syncobj_timeline_unref(state->acquire_timeline); wlr_drm_syncobj_timeline_unref(state->release_timeline); + wlr_drm_syncobj_merger_unref(state->release_merger); } static void surface_synced_move_state(void *_dst, void *_src) { struct wlr_linux_drm_syncobj_surface_v1_state *dst = _dst, *src = _src; - // TODO: immediately signal dst.release_timeline if necessary + if (src->acquire_timeline == NULL) { + // ignore commits that did not attach a buffer + return; + } surface_synced_finish_state(dst); *dst = *src; *src = (struct wlr_linux_drm_syncobj_surface_v1_state){0}; } +static void surface_synced_commit(struct wlr_surface_synced *synced) { + struct wlr_linux_drm_syncobj_surface_v1 *surface = wl_container_of(synced, surface, synced); + + if (surface->current.release_merger != NULL) { + // ignore commits that did not attach a buffer + return; + } + + surface->current.release_merger = wlr_drm_syncobj_merger_create( + surface->current.release_timeline, surface->current.release_point); +} + + static const struct wlr_surface_synced_impl surface_synced_impl = { .state_size = sizeof(struct wlr_linux_drm_syncobj_surface_v1_state), .finish_state = surface_synced_finish_state, .move_state = surface_synced_move_state, + .commit = surface_synced_commit, }; static void manager_handle_destroy(struct wl_client *client, @@ -422,6 +442,11 @@ struct wlr_linux_drm_syncobj_manager_v1 *wlr_linux_drm_syncobj_manager_v1_create struct wl_display *display, uint32_t version, int drm_fd) { assert(version <= LINUX_DRM_SYNCOBJ_V1_VERSION); + if (!HAVE_LINUX_SYNC_FILE) { + wlr_log(WLR_INFO, "Linux sync_file unavailable, disabling linux-drm-syncobj-v1"); + return NULL; + } + if (!check_syncobj_eventfd(drm_fd)) { wlr_log(WLR_INFO, "DRM syncobj eventfd unavailable, disabling linux-drm-syncobj-v1"); return NULL; @@ -467,16 +492,14 @@ wlr_linux_drm_syncobj_v1_get_surface_state(struct wlr_surface *wlr_surface) { } struct release_signaller { - struct wlr_drm_syncobj_timeline *timeline; - uint64_t point; + struct wlr_drm_syncobj_merger *merger; struct wl_listener buffer_release; }; static void release_signaller_handle_buffer_release(struct wl_listener *listener, void *data) { struct release_signaller *signaller = wl_container_of(listener, signaller, buffer_release); - wlr_drm_syncobj_timeline_signal(signaller->timeline, signaller->point); - wlr_drm_syncobj_timeline_unref(signaller->timeline); + wlr_drm_syncobj_merger_unref(signaller->merger); wl_list_remove(&signaller->buffer_release.link); free(signaller); } @@ -496,11 +519,17 @@ bool wlr_linux_drm_syncobj_v1_state_signal_release_with_buffer( return false; } - signaller->timeline = wlr_drm_syncobj_timeline_ref(state->release_timeline); - signaller->point = state->release_point; - + signaller->merger = wlr_drm_syncobj_merger_ref(state->release_merger); signaller->buffer_release.notify = release_signaller_handle_buffer_release; wl_signal_add(&buffer->events.release, &signaller->buffer_release); return true; } + +bool wlr_linux_drm_syncobj_v1_state_add_release_point( + struct wlr_linux_drm_syncobj_surface_v1_state *state, + struct wlr_drm_syncobj_timeline *release_timeline, uint64_t release_point, + struct wl_event_loop *event_loop) { + return wlr_drm_syncobj_merger_add(state->release_merger, + release_timeline, release_point, event_loop); +} From b2f6a390a490cdc423fd1fe880bf226708b22951 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Poisot?= Date: Fri, 31 Jan 2025 18:49:48 +0000 Subject: [PATCH 298/311] scene: transfer sample syncobj to client timeline --- types/scene/surface.c | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/types/scene/surface.c b/types/scene/surface.c index 7aa75c6d4..e6ea1333a 100644 --- a/types/scene/surface.c +++ b/types/scene/surface.c @@ -159,6 +159,13 @@ static void handle_scene_buffer_output_sample( } else { wlr_presentation_surface_textured_on_output(surface->surface, output); } + + struct wlr_linux_drm_syncobj_surface_v1_state *syncobj_surface_state = + wlr_linux_drm_syncobj_v1_get_surface_state(surface->surface); + if (syncobj_surface_state != NULL && event->release_timeline != NULL) { + wlr_linux_drm_syncobj_v1_state_add_release_point(syncobj_surface_state, + event->release_timeline, event->release_point, output->event_loop); + } } static void handle_scene_buffer_frame_done( @@ -323,27 +330,15 @@ static void surface_reconfigure(struct wlr_scene_surface *scene_surface) { struct wlr_linux_drm_syncobj_surface_v1_state *syncobj_surface_state = wlr_linux_drm_syncobj_v1_get_surface_state(surface); - struct wlr_drm_syncobj_timeline *wait_timeline = NULL; - uint64_t wait_point = 0; - if (syncobj_surface_state != NULL) { - wait_timeline = syncobj_surface_state->acquire_timeline; - wait_point = syncobj_surface_state->acquire_point; - } - struct wlr_scene_buffer_set_buffer_options options = { .damage = &surface->buffer_damage, - .wait_timeline = wait_timeline, - .wait_point = wait_point, }; + if (syncobj_surface_state != NULL) { + options.wait_timeline = syncobj_surface_state->acquire_timeline; + options.wait_point = syncobj_surface_state->acquire_point; + } wlr_scene_buffer_set_buffer_with_options(scene_buffer, &surface->buffer->base, &options); - - if (syncobj_surface_state != NULL && - (surface->current.committed & WLR_SURFACE_STATE_BUFFER) && - surface->buffer->source != NULL) { - wlr_linux_drm_syncobj_v1_state_signal_release_with_buffer(syncobj_surface_state, - surface->buffer->source); - } } else { wlr_scene_buffer_set_buffer(scene_buffer, NULL); } From cd555f9261d5443ba13f484fe4baa6dc3978c859 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Poisot?= Date: Fri, 31 Jan 2025 18:59:10 +0000 Subject: [PATCH 299/311] backend/drm: properly delay syncobj signalling DRM CRTC signals when scanout begins, but wlr_output_state_set_signal_timeline() is defined to signal buffer release. Delay to the next page flip --- backend/drm/atomic.c | 9 +++++++-- backend/drm/drm.c | 30 ++++++++++++++++++++++++++++++ include/backend/drm/drm.h | 4 ++++ 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/backend/drm/atomic.c b/backend/drm/atomic.c index 9bccff6ce..f12c56907 100644 --- a/backend/drm/atomic.c +++ b/backend/drm/atomic.c @@ -426,8 +426,13 @@ void drm_atomic_connector_apply_commit(struct wlr_drm_connector_state *state) { } if (state->out_fence_fd >= 0) { // TODO: error handling - wlr_drm_syncobj_timeline_import_sync_file(state->base->signal_timeline, - state->base->signal_point, state->out_fence_fd); + if (crtc->primary->current_release_timeline != NULL) { + wlr_drm_syncobj_timeline_import_sync_file(crtc->primary->current_release_timeline, + crtc->primary->current_release_point, state->out_fence_fd); + wlr_drm_syncobj_timeline_unref(crtc->primary->current_release_timeline); + crtc->primary->current_release_timeline = NULL; + crtc->primary->current_release_point = 0; + } close(state->out_fence_fd); } diff --git a/backend/drm/drm.c b/backend/drm/drm.c index 9b90022d2..7cfea5a14 100644 --- a/backend/drm/drm.c +++ b/backend/drm/drm.c @@ -368,7 +368,17 @@ static void drm_plane_finish_surface(struct wlr_drm_plane *plane) { } drm_fb_clear(&plane->queued_fb); + if (plane->queued_release_timeline != NULL) { + wlr_drm_syncobj_timeline_signal(plane->queued_release_timeline, plane->queued_release_point); + wlr_drm_syncobj_timeline_unref(plane->queued_release_timeline); + plane->queued_release_timeline = NULL; + } drm_fb_clear(&plane->current_fb); + if (plane->current_release_timeline != NULL) { + wlr_drm_syncobj_timeline_signal(plane->current_release_timeline, plane->current_release_point); + wlr_drm_syncobj_timeline_unref(plane->current_release_timeline); + plane->current_release_timeline = NULL; + } finish_drm_surface(&plane->mgpu_surf); } @@ -557,6 +567,18 @@ static void drm_connector_apply_commit(const struct wlr_drm_connector_state *sta struct wlr_drm_crtc *crtc = conn->crtc; drm_fb_copy(&crtc->primary->queued_fb, state->primary_fb); + if (crtc->primary->queued_release_timeline != NULL) { + wlr_drm_syncobj_timeline_signal(crtc->primary->queued_release_timeline, crtc->primary->queued_release_point); + wlr_drm_syncobj_timeline_unref(crtc->primary->queued_release_timeline); + } + if (state->base->signal_timeline != NULL) { + crtc->primary->queued_release_timeline = wlr_drm_syncobj_timeline_ref(state->base->signal_timeline); + crtc->primary->queued_release_point = state->base->signal_point; + } else { + crtc->primary->queued_release_timeline = NULL; + crtc->primary->queued_release_point = 0; + } + crtc->primary->viewport = state->primary_viewport; if (crtc->cursor != NULL) { drm_fb_copy(&crtc->cursor->queued_fb, state->cursor_fb); @@ -2019,6 +2041,14 @@ static void handle_page_flip(int fd, unsigned seq, struct wlr_drm_plane *plane = conn->crtc->primary; if (plane->queued_fb) { drm_fb_move(&plane->current_fb, &plane->queued_fb); + if (plane->current_release_timeline != NULL) { + wlr_drm_syncobj_timeline_signal(plane->current_release_timeline, plane->current_release_point); + wlr_drm_syncobj_timeline_unref(plane->current_release_timeline); + } + plane->current_release_timeline = plane->queued_release_timeline; + plane->current_release_point = plane->queued_release_point; + plane->queued_release_timeline = NULL; + plane->queued_release_point = 0; } if (conn->crtc->cursor && conn->crtc->cursor->queued_fb) { drm_fb_move(&conn->crtc->cursor->current_fb, diff --git a/include/backend/drm/drm.h b/include/backend/drm/drm.h index af4231f54..e07c16efe 100644 --- a/include/backend/drm/drm.h +++ b/include/backend/drm/drm.h @@ -29,8 +29,12 @@ struct wlr_drm_plane { /* Buffer submitted to the kernel, will be presented on next vblank */ struct wlr_drm_fb *queued_fb; + struct wlr_drm_syncobj_timeline *queued_release_timeline; + uint64_t queued_release_point; /* Buffer currently displayed on screen */ struct wlr_drm_fb *current_fb; + struct wlr_drm_syncobj_timeline *current_release_timeline; + uint64_t current_release_point; /* Viewport belonging to the last committed fb */ struct wlr_drm_viewport viewport; From 8d454e1e34988868b45693c8e2178c70bd19ad2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Poisot?= Date: Fri, 14 Mar 2025 12:16:21 +0000 Subject: [PATCH 300/311] output/drm: don't use OUT_FENCE_PTR The returned fence is not required to be signalled at the earliest possible time. It is not intended to replace the drm flip event, and is expected to be signalled only much later --- backend/drm/atomic.c | 30 ------------------------------ backend/drm/drm.c | 1 - backend/drm/libliftoff.c | 4 ---- include/backend/drm/drm.h | 2 +- 4 files changed, 1 insertion(+), 36 deletions(-) diff --git a/backend/drm/atomic.c b/backend/drm/atomic.c index f12c56907..daa8ba9bf 100644 --- a/backend/drm/atomic.c +++ b/backend/drm/atomic.c @@ -424,17 +424,6 @@ void drm_atomic_connector_apply_commit(struct wlr_drm_connector_state *state) { if (state->primary_in_fence_fd >= 0) { close(state->primary_in_fence_fd); } - if (state->out_fence_fd >= 0) { - // TODO: error handling - if (crtc->primary->current_release_timeline != NULL) { - wlr_drm_syncobj_timeline_import_sync_file(crtc->primary->current_release_timeline, - crtc->primary->current_release_point, state->out_fence_fd); - wlr_drm_syncobj_timeline_unref(crtc->primary->current_release_timeline); - crtc->primary->current_release_timeline = NULL; - crtc->primary->current_release_point = 0; - } - close(state->out_fence_fd); - } conn->colorspace = state->colorspace; } @@ -452,9 +441,6 @@ void drm_atomic_connector_rollback_commit(struct wlr_drm_connector_state *state) if (state->primary_in_fence_fd >= 0) { close(state->primary_in_fence_fd); } - if (state->out_fence_fd >= 0) { - close(state->out_fence_fd); - } } static void plane_disable(struct atomic *atom, struct wlr_drm_plane *plane) { @@ -562,19 +548,6 @@ static void set_plane_in_fence_fd(struct atomic *atom, atomic_add(atom, plane->id, plane->props.in_fence_fd, sync_file_fd); } -static void set_crtc_out_fence_ptr(struct atomic *atom, struct wlr_drm_crtc *crtc, - int *fd_ptr) { - if (!crtc->props.out_fence_ptr) { - wlr_log(WLR_ERROR, - "CRTC %"PRIu32" is missing the OUT_FENCE_PTR property", - crtc->id); - atom->failed = true; - return; - } - - atomic_add(atom, crtc->id, crtc->props.out_fence_ptr, (uintptr_t)fd_ptr); -} - static void atomic_connector_add(struct atomic *atom, struct wlr_drm_connector_state *state, bool modeset) { struct wlr_drm_connector *conn = state->connector; @@ -623,9 +596,6 @@ static void atomic_connector_add(struct atomic *atom, if (state->primary_in_fence_fd >= 0) { set_plane_in_fence_fd(atom, crtc->primary, state->primary_in_fence_fd); } - if (state->base->committed & WLR_OUTPUT_STATE_SIGNAL_TIMELINE) { - set_crtc_out_fence_ptr(atom, crtc, &state->out_fence_fd); - } if (crtc->cursor) { if (drm_connector_is_cursor_visible(conn)) { struct wlr_fbox cursor_src = { diff --git a/backend/drm/drm.c b/backend/drm/drm.c index 7cfea5a14..d2f75f71f 100644 --- a/backend/drm/drm.c +++ b/backend/drm/drm.c @@ -669,7 +669,6 @@ static void drm_connector_state_init(struct wlr_drm_connector_state *state, .base = base, .active = output_pending_enabled(&conn->output, base), .primary_in_fence_fd = -1, - .out_fence_fd = -1, }; struct wlr_output_mode *mode = conn->output.current_mode; diff --git a/backend/drm/libliftoff.c b/backend/drm/libliftoff.c index 12761afd4..333beacad 100644 --- a/backend/drm/libliftoff.c +++ b/backend/drm/libliftoff.c @@ -352,10 +352,6 @@ static bool add_connector(drmModeAtomicReq *req, liftoff_layer_set_property(crtc->liftoff_composition_layer, "IN_FENCE_FD", state->primary_in_fence_fd); } - if (state->base->committed & WLR_OUTPUT_STATE_SIGNAL_TIMELINE) { - ok = ok && add_prop(req, crtc->id, crtc->props.out_fence_ptr, - (uintptr_t)&state->out_fence_fd); - } if (state->base->committed & WLR_OUTPUT_STATE_LAYERS) { for (size_t i = 0; i < state->base->layers_len; i++) { diff --git a/include/backend/drm/drm.h b/include/backend/drm/drm.h index e07c16efe..a8c5e077a 100644 --- a/include/backend/drm/drm.h +++ b/include/backend/drm/drm.h @@ -160,7 +160,7 @@ struct wlr_drm_connector_state { uint32_t mode_id; uint32_t gamma_lut; uint32_t fb_damage_clips; - int primary_in_fence_fd, out_fence_fd; + int primary_in_fence_fd; bool vrr_enabled; uint32_t colorspace; uint32_t hdr_output_metadata; From 627da39e76d1f5a3cd18730bd305a0351bb1a121 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Thu, 19 Mar 2026 20:14:53 +0100 Subject: [PATCH 301/311] build: bump version to 0.21.0-dev --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 27aae8e9f..6d31bdc41 100644 --- a/meson.build +++ b/meson.build @@ -1,7 +1,7 @@ project( 'wlroots', 'c', - version: '0.20.0-rc4', + version: '0.21.0-dev', license: 'MIT', meson_version: '>=1.3', default_options: [ From 8fe3034948fd904a9000a55b8940dd30797c8d0b Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Thu, 19 Mar 2026 20:23:35 +0100 Subject: [PATCH 302/311] tinywl: bump wlroots version to 0.21 --- tinywl/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tinywl/Makefile b/tinywl/Makefile index 2efe4a436..0f1c4cb13 100644 --- a/tinywl/Makefile +++ b/tinywl/Makefile @@ -1,6 +1,6 @@ PKG_CONFIG?=pkg-config -PKGS="wlroots-0.20" wayland-server xkbcommon +PKGS="wlroots-0.21" wayland-server xkbcommon CFLAGS_PKG_CONFIG!=$(PKG_CONFIG) --cflags $(PKGS) CFLAGS+=$(CFLAGS_PKG_CONFIG) LIBS!=$(PKG_CONFIG) --libs $(PKGS) From 7287f700ab50874e7bdcc6bd5f4b079dbbe745ef Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Fri, 13 Mar 2026 17:32:38 +0100 Subject: [PATCH 303/311] color_management_v1: use early continue in surface loop --- types/wlr_color_management_v1.c | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/types/wlr_color_management_v1.c b/types/wlr_color_management_v1.c index 0a19e8e78..6f00c0710 100644 --- a/types/wlr_color_management_v1.c +++ b/types/wlr_color_management_v1.c @@ -1002,16 +1002,19 @@ void wlr_color_manager_v1_set_surface_preferred_image_description( struct wlr_color_management_surface_feedback_v1 *surface_feedback; wl_list_for_each(surface_feedback, &manager->surface_feedbacks, link) { - if (surface_feedback->surface == surface) { - surface_feedback->data = *data; - uint32_t version = wl_resource_get_version(surface_feedback->resource); - if (version >= WP_COLOR_MANAGEMENT_SURFACE_FEEDBACK_V1_PREFERRED_CHANGED2_SINCE_VERSION) { - wp_color_management_surface_feedback_v1_send_preferred_changed2( - surface_feedback->resource, identity_hi, identity_lo); - } else { - wp_color_management_surface_feedback_v1_send_preferred_changed( - surface_feedback->resource, identity_lo); - } + if (surface_feedback->surface != surface) { + continue; + } + + surface_feedback->data = *data; + + uint32_t version = wl_resource_get_version(surface_feedback->resource); + if (version >= WP_COLOR_MANAGEMENT_SURFACE_FEEDBACK_V1_PREFERRED_CHANGED2_SINCE_VERSION) { + wp_color_management_surface_feedback_v1_send_preferred_changed2( + surface_feedback->resource, identity_hi, identity_lo); + } else { + wp_color_management_surface_feedback_v1_send_preferred_changed( + surface_feedback->resource, identity_lo); } } } From 4ca40004fd8f35a85f4f342c6b12d3792fbb23ea Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Fri, 13 Mar 2026 17:37:20 +0100 Subject: [PATCH 304/311] color_management_v1: ignore surface update if no-op If the new image description is identical to the old one, skip the event. --- types/wlr_color_management_v1.c | 38 ++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/types/wlr_color_management_v1.c b/types/wlr_color_management_v1.c index 6f00c0710..aa924e36d 100644 --- a/types/wlr_color_management_v1.c +++ b/types/wlr_color_management_v1.c @@ -74,6 +74,41 @@ static float decode_cie1931_coord(int32_t raw) { return (float)raw / (1000 * 1000); } +static bool cie1931_xy_equal(const struct wlr_color_cie1931_xy *a, + const struct wlr_color_cie1931_xy *b) { + return a->x == b->x && a->y == b->y; +} + +static bool primaries_equal(const struct wlr_color_primaries *a, + const struct wlr_color_primaries *b) { + return cie1931_xy_equal(&a->red, &b->red) && + cie1931_xy_equal(&a->green, &b->green) && + cie1931_xy_equal(&a->blue, &b->blue) && + cie1931_xy_equal(&a->white, &b->white); +} + +static bool img_desc_data_equal(const struct wlr_image_description_v1_data *a, + const struct wlr_image_description_v1_data *b) { + if (a->tf_named != b->tf_named || + a->primaries_named != b->primaries_named || + a->has_mastering_display_primaries != b->has_mastering_display_primaries || + a->has_mastering_luminance != b->has_mastering_luminance || + a->max_cll != b->max_cll || + a->max_fall != b->max_fall) { + return false; + } + if (a->has_mastering_display_primaries && + !primaries_equal(&a->mastering_display_primaries, &b->mastering_display_primaries)) { + return false; + } + if (a->has_mastering_luminance && + (a->mastering_luminance.min != b->mastering_luminance.min || + a->mastering_luminance.max != b->mastering_luminance.max)) { + return false; + } + return true; +} + static const struct wp_image_description_v1_interface image_desc_impl; static struct wlr_image_description_v1 *image_desc_from_resource(struct wl_resource *resource) { @@ -1002,7 +1037,8 @@ void wlr_color_manager_v1_set_surface_preferred_image_description( struct wlr_color_management_surface_feedback_v1 *surface_feedback; wl_list_for_each(surface_feedback, &manager->surface_feedbacks, link) { - if (surface_feedback->surface != surface) { + if (surface_feedback->surface != surface || + img_desc_data_equal(&surface_feedback->data, data)) { continue; } From fd870f6d27601063bad2219308a80775a9b9a12b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Poisot?= Date: Fri, 20 Mar 2026 12:35:21 +0000 Subject: [PATCH 305/311] linux_drm_syncobj_v1: fix handling of empty first commit As reported in https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/4979#note_3385626, bfd6e619fc8642fb3f2cca6f9cb94d257def8780 did not correctly handle clients that don't immediately follow their call to `wp_linux_drm_syncobj_manager_v1.get_surface` with a commit attaching a buffer Fixes: bfd6e619fc8642fb3f2cca6f9cb94d257def8780 --- include/wlr/types/wlr_linux_drm_syncobj_v1.h | 1 + types/wlr_linux_drm_syncobj_v1.c | 13 ++++++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/include/wlr/types/wlr_linux_drm_syncobj_v1.h b/include/wlr/types/wlr_linux_drm_syncobj_v1.h index 70a3f547f..7fd55ec2e 100644 --- a/include/wlr/types/wlr_linux_drm_syncobj_v1.h +++ b/include/wlr/types/wlr_linux_drm_syncobj_v1.h @@ -20,6 +20,7 @@ struct wlr_linux_drm_syncobj_surface_v1_state { uint64_t acquire_point; struct { + bool committed; struct wlr_drm_syncobj_timeline *release_timeline; uint64_t release_point; struct wlr_drm_syncobj_merger *release_merger; diff --git a/types/wlr_linux_drm_syncobj_v1.c b/types/wlr_linux_drm_syncobj_v1.c index 200f4c9e6..53fc2fd43 100644 --- a/types/wlr_linux_drm_syncobj_v1.c +++ b/types/wlr_linux_drm_syncobj_v1.c @@ -171,19 +171,20 @@ static void surface_synced_move_state(void *_dst, void *_src) { } surface_synced_finish_state(dst); *dst = *src; + dst->committed = true; *src = (struct wlr_linux_drm_syncobj_surface_v1_state){0}; } static void surface_synced_commit(struct wlr_surface_synced *synced) { struct wlr_linux_drm_syncobj_surface_v1 *surface = wl_container_of(synced, surface, synced); - if (surface->current.release_merger != NULL) { - // ignore commits that did not attach a buffer + if (!surface->current.committed) { return; } surface->current.release_merger = wlr_drm_syncobj_merger_create( surface->current.release_timeline, surface->current.release_point); + surface->current.committed = false; } @@ -507,7 +508,7 @@ static void release_signaller_handle_buffer_release(struct wl_listener *listener bool wlr_linux_drm_syncobj_v1_state_signal_release_with_buffer( struct wlr_linux_drm_syncobj_surface_v1_state *state, struct wlr_buffer *buffer) { assert(buffer->n_locks > 0); - if (state->release_timeline == NULL) { + if (state->release_merger == NULL) { // This can happen if an existing surface with a buffer has a // syncobj_surface_v1_state created but no new buffer with release // timeline committed. @@ -530,6 +531,12 @@ bool wlr_linux_drm_syncobj_v1_state_add_release_point( struct wlr_linux_drm_syncobj_surface_v1_state *state, struct wlr_drm_syncobj_timeline *release_timeline, uint64_t release_point, struct wl_event_loop *event_loop) { + if (state->release_merger == NULL) { + // This can happen if an existing surface with a buffer has a + // syncobj_surface_v1_state created but no new buffer with release + // timeline committed. + return true; + } return wlr_drm_syncobj_merger_add(state->release_merger, release_timeline, release_point, event_loop); } From 413664e0b016bb7dde644b4b5477f176d28e5631 Mon Sep 17 00:00:00 2001 From: Simon Zeni Date: Tue, 24 Mar 2026 09:59:18 -0400 Subject: [PATCH 306/311] render/vulkan: compile against vulkan 1.2 header Uses the EXT version of VK_PIPELINE_COMPILE_REQUIRED in `vulkan_strerror` func since it requires Vulkan 1.3, switch to VK_EXT_global_priority instead of VK_KHR_global_priority which is only promoted to core in Vulkan 1.3 as well. --- render/vulkan/util.c | 2 +- render/vulkan/vulkan.c | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/render/vulkan/util.c b/render/vulkan/util.c index 8c31dc797..73a14073a 100644 --- a/render/vulkan/util.c +++ b/render/vulkan/util.c @@ -44,7 +44,7 @@ const char *vulkan_strerror(VkResult err) { ERR_STR(ERROR_INVALID_EXTERNAL_HANDLE); ERR_STR(ERROR_FRAGMENTATION); ERR_STR(ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS); - ERR_STR(PIPELINE_COMPILE_REQUIRED); + ERR_STR(PIPELINE_COMPILE_REQUIRED_EXT); ERR_STR(ERROR_SURFACE_LOST_KHR); ERR_STR(ERROR_NATIVE_WINDOW_IN_USE_KHR); ERR_STR(SUBOPTIMAL_KHR); diff --git a/render/vulkan/vulkan.c b/render/vulkan/vulkan.c index d1a0d77d3..b4a5e0e11 100644 --- a/render/vulkan/vulkan.c +++ b/render/vulkan/vulkan.c @@ -556,17 +556,17 @@ struct wlr_vk_device *vulkan_device_create(struct wlr_vk_instance *ini, .pQueuePriorities = &prio, }; - VkDeviceQueueGlobalPriorityCreateInfoKHR global_priority; + VkDeviceQueueGlobalPriorityCreateInfoEXT global_priority; bool has_global_priority = check_extension(avail_ext_props, avail_extc, - VK_KHR_GLOBAL_PRIORITY_EXTENSION_NAME); + VK_EXT_GLOBAL_PRIORITY_EXTENSION_NAME); if (has_global_priority) { // If global priorities are supported, request a high-priority context - global_priority = (VkDeviceQueueGlobalPriorityCreateInfoKHR){ - .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_GLOBAL_PRIORITY_CREATE_INFO_KHR, - .globalPriority = VK_QUEUE_GLOBAL_PRIORITY_HIGH_KHR, + global_priority = (VkDeviceQueueGlobalPriorityCreateInfoEXT){ + .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_GLOBAL_PRIORITY_CREATE_INFO_EXT, + .globalPriority = VK_QUEUE_GLOBAL_PRIORITY_HIGH_EXT, }; qinfo.pNext = &global_priority; - extensions[extensions_len++] = VK_KHR_GLOBAL_PRIORITY_EXTENSION_NAME; + extensions[extensions_len++] = VK_EXT_GLOBAL_PRIORITY_EXTENSION_NAME; wlr_log(WLR_DEBUG, "Requesting a high-priority device queue"); } else { wlr_log(WLR_DEBUG, "Global priorities are not supported, " From f295d0322aea328f32cfd418459b8331e613b466 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Poisot?= Date: Thu, 26 Mar 2026 12:25:07 +0000 Subject: [PATCH 307/311] render: explicit sync for wlr_texture_read_pixels() --- include/render/vulkan.h | 7 ++- include/wlr/render/wlr_texture.h | 2 + render/gles2/texture.c | 23 ++++++++++ render/vulkan/pass.c | 53 ++-------------------- render/vulkan/renderer.c | 77 ++++++++++++++++++++++++++++++-- render/vulkan/texture.c | 3 +- 6 files changed, 110 insertions(+), 55 deletions(-) diff --git a/include/render/vulkan.h b/include/render/vulkan.h index c5d571ef7..82d3e40fd 100644 --- a/include/render/vulkan.h +++ b/include/render/vulkan.h @@ -410,7 +410,7 @@ VkCommandBuffer vulkan_record_stage_cb(struct wlr_vk_renderer *renderer); // Submits the current stage command buffer and waits until it has // finished execution. -bool vulkan_submit_stage_wait(struct wlr_vk_renderer *renderer); +bool vulkan_submit_stage_wait(struct wlr_vk_renderer *renderer, int wait_sync_file_fd); struct wlr_vk_render_pass_texture { struct wlr_vk_texture *texture; @@ -476,6 +476,8 @@ uint64_t vulkan_end_command_buffer(struct wlr_vk_command_buffer *cb, void vulkan_reset_command_buffer(struct wlr_vk_command_buffer *cb); bool vulkan_wait_command_buffer(struct wlr_vk_command_buffer *cb, struct wlr_vk_renderer *renderer); +VkSemaphore vulkan_command_buffer_wait_sync_file(struct wlr_vk_renderer *renderer, + struct wlr_vk_command_buffer *render_cb, size_t sem_index, int sync_file_fd); bool vulkan_sync_render_pass_release(struct wlr_vk_renderer *renderer, struct wlr_vk_render_pass *pass); @@ -488,7 +490,8 @@ bool vulkan_read_pixels(struct wlr_vk_renderer *vk_renderer, VkFormat src_format, VkImage src_image, uint32_t drm_format, uint32_t stride, uint32_t width, uint32_t height, uint32_t src_x, uint32_t src_y, - uint32_t dst_x, uint32_t dst_y, void *data); + uint32_t dst_x, uint32_t dst_y, void *data, + struct wlr_drm_syncobj_timeline *wait_timeline, uint64_t wait_point); // State (e.g. image texture) associated with a surface. struct wlr_vk_texture { diff --git a/include/wlr/render/wlr_texture.h b/include/wlr/render/wlr_texture.h index 1e352c6e6..b7dcb143b 100644 --- a/include/wlr/render/wlr_texture.h +++ b/include/wlr/render/wlr_texture.h @@ -37,6 +37,8 @@ struct wlr_texture_read_pixels_options { uint32_t dst_x, dst_y; /** Source box of the texture to read from. If empty, the full texture is assumed. */ const struct wlr_box src_box; + struct wlr_drm_syncobj_timeline *wait_timeline; + uint64_t wait_point; }; bool wlr_texture_read_pixels(struct wlr_texture *texture, diff --git a/render/gles2/texture.c b/render/gles2/texture.c index 7460755cd..575a11fca 100644 --- a/render/gles2/texture.c +++ b/render/gles2/texture.c @@ -4,8 +4,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -201,6 +203,27 @@ static bool gles2_texture_read_pixels(struct wlr_texture *wlr_texture, return false; } + if (options->wait_timeline != NULL) { + int sync_file_fd = + wlr_drm_syncobj_timeline_export_sync_file(options->wait_timeline, options->wait_point); + if (sync_file_fd < 0) { + return false; + } + + struct wlr_gles2_renderer *renderer = texture->renderer; + EGLSyncKHR sync = wlr_egl_create_sync(renderer->egl, sync_file_fd); + close(sync_file_fd); + if (sync == EGL_NO_SYNC_KHR) { + return false; + } + + bool ok = wlr_egl_wait_sync(renderer->egl, sync); + wlr_egl_destroy_sync(renderer->egl, sync); + if (!ok) { + return false; + } + } + // Make sure any pending drawing is finished before we try to read it glFinish(); diff --git a/render/vulkan/pass.c b/render/vulkan/pass.c index 503e37c07..becd060f4 100644 --- a/render/vulkan/pass.c +++ b/render/vulkan/pass.c @@ -99,53 +99,6 @@ static void render_pass_destroy(struct wlr_vk_render_pass *pass) { free(pass); } -static VkSemaphore render_pass_wait_sync_file(struct wlr_vk_render_pass *pass, - size_t sem_index, int sync_file_fd) { - struct wlr_vk_renderer *renderer = pass->renderer; - struct wlr_vk_command_buffer *render_cb = pass->command_buffer; - VkResult res; - - VkSemaphore *wait_semaphores = render_cb->wait_semaphores.data; - size_t wait_semaphores_len = render_cb->wait_semaphores.size / sizeof(wait_semaphores[0]); - - VkSemaphore *sem_ptr; - if (sem_index >= wait_semaphores_len) { - sem_ptr = wl_array_add(&render_cb->wait_semaphores, sizeof(*sem_ptr)); - if (sem_ptr == NULL) { - return VK_NULL_HANDLE; - } - *sem_ptr = VK_NULL_HANDLE; - } else { - sem_ptr = &wait_semaphores[sem_index]; - } - - if (*sem_ptr == VK_NULL_HANDLE) { - VkSemaphoreCreateInfo semaphore_info = { - .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, - }; - res = vkCreateSemaphore(renderer->dev->dev, &semaphore_info, NULL, sem_ptr); - if (res != VK_SUCCESS) { - wlr_vk_error("vkCreateSemaphore", res); - return VK_NULL_HANDLE; - } - } - - VkImportSemaphoreFdInfoKHR import_info = { - .sType = VK_STRUCTURE_TYPE_IMPORT_SEMAPHORE_FD_INFO_KHR, - .handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT, - .flags = VK_SEMAPHORE_IMPORT_TEMPORARY_BIT, - .semaphore = *sem_ptr, - .fd = sync_file_fd, - }; - res = renderer->dev->api.vkImportSemaphoreFdKHR(renderer->dev->dev, &import_info); - if (res != VK_SUCCESS) { - wlr_vk_error("vkImportSemaphoreFdKHR", res); - return VK_NULL_HANDLE; - } - - return *sem_ptr; -} - static bool render_pass_wait_render_buffer(struct wlr_vk_render_pass *pass, VkSemaphoreSubmitInfoKHR *render_wait, uint32_t *render_wait_len_ptr) { int sync_file_fds[WLR_DMABUF_MAX_PLANES]; @@ -162,7 +115,8 @@ static bool render_pass_wait_render_buffer(struct wlr_vk_render_pass *pass, continue; } - VkSemaphore sem = render_pass_wait_sync_file(pass, *render_wait_len_ptr, sync_file_fds[i]); + VkSemaphore sem = vulkan_command_buffer_wait_sync_file(pass->renderer, + pass->command_buffer, *render_wait_len_ptr, sync_file_fds[i]); if (sem == VK_NULL_HANDLE) { close(sync_file_fds[i]); continue; @@ -431,7 +385,8 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { continue; } - VkSemaphore sem = render_pass_wait_sync_file(pass, render_wait_len, sync_file_fds[i]); + VkSemaphore sem = vulkan_command_buffer_wait_sync_file(renderer, render_cb, + render_wait_len, sync_file_fds[i]); if (sem == VK_NULL_HANDLE) { close(sync_file_fds[i]); continue; diff --git a/render/vulkan/renderer.c b/render/vulkan/renderer.c index 52c87e9e2..e04f95a0a 100644 --- a/render/vulkan/renderer.c +++ b/render/vulkan/renderer.c @@ -379,7 +379,52 @@ VkCommandBuffer vulkan_record_stage_cb(struct wlr_vk_renderer *renderer) { return renderer->stage.cb->vk; } -bool vulkan_submit_stage_wait(struct wlr_vk_renderer *renderer) { +VkSemaphore vulkan_command_buffer_wait_sync_file(struct wlr_vk_renderer *renderer, + struct wlr_vk_command_buffer *render_cb, size_t sem_index, int sync_file_fd) { + VkResult res; + + VkSemaphore *wait_semaphores = render_cb->wait_semaphores.data; + size_t wait_semaphores_len = render_cb->wait_semaphores.size / sizeof(wait_semaphores[0]); + + VkSemaphore *sem_ptr; + if (sem_index >= wait_semaphores_len) { + sem_ptr = wl_array_add(&render_cb->wait_semaphores, sizeof(*sem_ptr)); + if (sem_ptr == NULL) { + return VK_NULL_HANDLE; + } + *sem_ptr = VK_NULL_HANDLE; + } else { + sem_ptr = &wait_semaphores[sem_index]; + } + + if (*sem_ptr == VK_NULL_HANDLE) { + VkSemaphoreCreateInfo semaphore_info = { + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, + }; + res = vkCreateSemaphore(renderer->dev->dev, &semaphore_info, NULL, sem_ptr); + if (res != VK_SUCCESS) { + wlr_vk_error("vkCreateSemaphore", res); + return VK_NULL_HANDLE; + } + } + + VkImportSemaphoreFdInfoKHR import_info = { + .sType = VK_STRUCTURE_TYPE_IMPORT_SEMAPHORE_FD_INFO_KHR, + .handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT, + .flags = VK_SEMAPHORE_IMPORT_TEMPORARY_BIT, + .semaphore = *sem_ptr, + .fd = sync_file_fd, + }; + res = renderer->dev->api.vkImportSemaphoreFdKHR(renderer->dev->dev, &import_info); + if (res != VK_SUCCESS) { + wlr_vk_error("vkImportSemaphoreFdKHR", res); + return VK_NULL_HANDLE; + } + + return *sem_ptr; +} + +bool vulkan_submit_stage_wait(struct wlr_vk_renderer *renderer, int wait_sync_file_fd) { if (renderer->stage.cb == NULL) { return false; } @@ -389,9 +434,12 @@ bool vulkan_submit_stage_wait(struct wlr_vk_renderer *renderer) { uint64_t timeline_point = vulkan_end_command_buffer(cb, renderer); if (timeline_point == 0) { + close(wait_sync_file_fd); return false; } + VkSemaphore wait_semaphore; + VkPipelineStageFlags wait_stage = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT; VkTimelineSemaphoreSubmitInfoKHR timeline_submit_info = { .sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO_KHR, .signalSemaphoreValueCount = 1, @@ -405,6 +453,18 @@ bool vulkan_submit_stage_wait(struct wlr_vk_renderer *renderer) { .signalSemaphoreCount = 1, .pSignalSemaphores = &renderer->timeline_semaphore, }; + + if (wait_sync_file_fd != -1) { + wait_semaphore = vulkan_command_buffer_wait_sync_file(renderer, cb, 0, wait_sync_file_fd); + if (wait_semaphore == VK_NULL_HANDLE) { + close(wait_sync_file_fd); + return false; + } + submit_info.waitSemaphoreCount = 1; + submit_info.pWaitSemaphores = &wait_semaphore; + submit_info.pWaitDstStageMask = &wait_stage; + } + VkResult res = vkQueueSubmit(renderer->dev->queue, 1, &submit_info, VK_NULL_HANDLE); if (res != VK_SUCCESS) { wlr_vk_error("vkQueueSubmit", res); @@ -1218,7 +1278,8 @@ bool vulkan_read_pixels(struct wlr_vk_renderer *vk_renderer, VkFormat src_format, VkImage src_image, uint32_t drm_format, uint32_t stride, uint32_t width, uint32_t height, uint32_t src_x, uint32_t src_y, - uint32_t dst_x, uint32_t dst_y, void *data) { + uint32_t dst_x, uint32_t dst_y, void *data, + struct wlr_drm_syncobj_timeline *wait_timeline, uint64_t wait_point) { VkDevice dev = vk_renderer->dev->dev; const struct wlr_pixel_format_info *pixel_format_info = drm_get_pixel_format_info(drm_format); @@ -1404,7 +1465,17 @@ bool vulkan_read_pixels(struct wlr_vk_renderer *vk_renderer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_MEMORY_READ_BIT); - if (!vulkan_submit_stage_wait(vk_renderer)) { + int wait_sync_file_fd = -1; + if (wait_timeline != NULL) { + wait_sync_file_fd = wlr_drm_syncobj_timeline_export_sync_file(wait_timeline, wait_point); + if (wait_sync_file_fd < 0) { + wlr_log(WLR_ERROR, "Failed to export wait timeline point as sync_file"); + return false; + } + } + + if (!vulkan_submit_stage_wait(vk_renderer, wait_sync_file_fd)) { + close(wait_sync_file_fd); return false; } diff --git a/render/vulkan/texture.c b/render/vulkan/texture.c index d769a0b9e..c6365c90b 100644 --- a/render/vulkan/texture.c +++ b/render/vulkan/texture.c @@ -238,7 +238,8 @@ static bool vulkan_texture_read_pixels(struct wlr_texture *wlr_texture, void *p = wlr_texture_read_pixel_options_get_data(options); return vulkan_read_pixels(texture->renderer, texture->format->vk, texture->image, - options->format, options->stride, src.width, src.height, src.x, src.y, 0, 0, p); + options->format, options->stride, src.width, src.height, src.x, src.y, 0, 0, p, + options->wait_timeline, options->wait_point); } static uint32_t vulkan_texture_preferred_read_format(struct wlr_texture *wlr_texture) { From a1ed6fca52d4c461cbf9de73672269d3271b1a57 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Fri, 27 Mar 2026 18:09:29 +0100 Subject: [PATCH 308/311] render/drm_syncobj: fix flags docs for wlr_drm_syncobj_timeline_waiter_init() wlr_drm_syncobj_timeline_check() is a bit different because zero will error out if the point has not materialized yet. Kernel docs for struct drm_syncobj_eventfd: https://dri.freedesktop.org/docs/drm/gpu/drm-uapi.html#c.drm_syncobj_eventfd --- include/wlr/render/drm_syncobj.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/include/wlr/render/drm_syncobj.h b/include/wlr/render/drm_syncobj.h index fabe23d1c..c7dd3b34c 100644 --- a/include/wlr/render/drm_syncobj.h +++ b/include/wlr/render/drm_syncobj.h @@ -97,7 +97,11 @@ bool wlr_drm_syncobj_timeline_signal(struct wlr_drm_syncobj_timeline *timeline, /** * Asynchronously wait for a timeline point. * - * See wlr_drm_syncobj_timeline_check() for a definition of flags. + * Flags can be: + * + * - 0 to wait for the point to be signalled + * - DRM_SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE to only wait for a fence to + * materialize * * A callback must be provided that will be invoked when the waiter has finished. */ From 334019f839bf0728d958c179aceed67e0e8db66a Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Fri, 27 Mar 2026 18:20:40 +0100 Subject: [PATCH 309/311] render/drm_syncobj: use drmSyncobjEventfd() Avoids using a raw IOCTL directly. This function was introduced way back in libdrm 2.4.116. --- render/drm_syncobj.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/render/drm_syncobj.c b/render/drm_syncobj.c index 2abe2cff6..15f71c536 100644 --- a/render/drm_syncobj.c +++ b/render/drm_syncobj.c @@ -222,14 +222,8 @@ bool wlr_drm_syncobj_timeline_waiter_init(struct wlr_drm_syncobj_timeline_waiter return false; } - struct drm_syncobj_eventfd syncobj_eventfd = { - .handle = timeline->handle, - .flags = flags, - .point = point, - .fd = ev_fd, - }; - if (drmIoctl(timeline->drm_fd, DRM_IOCTL_SYNCOBJ_EVENTFD, &syncobj_eventfd) != 0) { - wlr_log_errno(WLR_ERROR, "DRM_IOCTL_SYNCOBJ_EVENTFD failed"); + if (drmSyncobjEventfd(timeline->drm_fd, timeline->handle, point, ev_fd, flags) != 0) { + wlr_log_errno(WLR_ERROR, "drmSyncobjEventfd() failed"); close(ev_fd); return false; } From e22084f63967aa72cb56873aeb48b01fb8dc2c9b Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Mon, 30 Mar 2026 11:45:54 +0200 Subject: [PATCH 310/311] ext_image_capture_source_v1/scene: fix extents Currently the width/height of the extents is too small if the first node visited has position/dimensions 0,0,100,100 and the second node has position/dimensions -20,-20,10,10. In this case the current code calculates total extents as -20,-20,100,100 but the correct extents are -20,-20,120,120. References: https://codeberg.org/river/river-classic/issues/17 --- types/ext_image_capture_source_v1/scene.c | 28 +++++++++++++---------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/types/ext_image_capture_source_v1/scene.c b/types/ext_image_capture_source_v1/scene.c index 99d34e012..7d4b8928a 100644 --- a/types/ext_image_capture_source_v1/scene.c +++ b/types/ext_image_capture_source_v1/scene.c @@ -34,13 +34,14 @@ struct scene_node_source_frame_event { static size_t last_output_num = 0; -static void _get_scene_node_extents(struct wlr_scene_node *node, struct wlr_box *box, int lx, int ly) { +static void _get_scene_node_extents(struct wlr_scene_node *node, int lx, int ly, + int *x_min, int *y_min, int *x_max, int *y_max) { switch (node->type) { case WLR_SCENE_NODE_TREE:; struct wlr_scene_tree *scene_tree = wlr_scene_tree_from_node(node); struct wlr_scene_node *child; wl_list_for_each(child, &scene_tree->children, link) { - _get_scene_node_extents(child, box, lx + child->x, ly + child->y); + _get_scene_node_extents(child, lx + child->x, ly + child->y, x_min, y_min, x_max, y_max); } break; case WLR_SCENE_NODE_RECT: @@ -48,27 +49,30 @@ static void _get_scene_node_extents(struct wlr_scene_node *node, struct wlr_box struct wlr_box node_box = { .x = lx, .y = ly }; scene_node_get_size(node, &node_box.width, &node_box.height); - if (node_box.x < box->x) { - box->x = node_box.x; + if (node_box.x < *x_min) { + *x_min = node_box.x; } - if (node_box.y < box->y) { - box->y = node_box.y; + if (node_box.y < *y_min) { + *y_min = node_box.y; } - if (node_box.x + node_box.width > box->x + box->width) { - box->width = node_box.x + node_box.width - box->x; + if (node_box.x + node_box.width > *x_max) { + *x_max = node_box.x + node_box.width; } - if (node_box.y + node_box.height > box->y + box->height) { - box->height = node_box.y + node_box.height - box->y; + if (node_box.y + node_box.height > *y_max) { + *y_max = node_box.y + node_box.height; } break; } } static void get_scene_node_extents(struct wlr_scene_node *node, struct wlr_box *box) { - *box = (struct wlr_box){ .x = INT_MAX, .y = INT_MAX }; int lx = 0, ly = 0; wlr_scene_node_coords(node, &lx, &ly); - _get_scene_node_extents(node, box, lx, ly); + *box = (struct wlr_box){ .x = INT_MAX, .y = INT_MAX }; + int x_max = INT_MIN, y_max = INT_MIN; + _get_scene_node_extents(node, lx, ly, &box->x, &box->y, &x_max, &y_max); + box->width = x_max - box->x; + box->height = y_max - box->y; } static void source_render(struct scene_node_source *source) { From c66a910753941c905299e62d9e31a4e90c0bbe98 Mon Sep 17 00:00:00 2001 From: Consolatis <40171-Consolatis@users.noreply.gitlab.freedesktop.org> Date: Thu, 2 Apr 2026 00:53:06 +0200 Subject: [PATCH 311/311] render/pixman: fix bilinear filtering to match gles2 renderer Before this patch the pixman renderer would use "constant padding" for bilinear scaling which meant that the edges would either be dark or turn transparent. The effect was most obvious when trying to scale a single row buffer to a height like 100. The center would have the desired color but the edges to both sides would fade into transparency. We now use PIXMAN_REPEAT_PAD which clamps out-of-bound pixels and seems to match the behavior of the gles2 renderer. --- render/pixman/pass.c | 1 + 1 file changed, 1 insertion(+) diff --git a/render/pixman/pass.c b/render/pixman/pass.c index d3ee17dca..af738bb42 100644 --- a/render/pixman/pass.c +++ b/render/pixman/pass.c @@ -159,6 +159,7 @@ static void render_pass_add_texture(struct wlr_render_pass *wlr_pass, switch (options->filter_mode) { case WLR_SCALE_FILTER_BILINEAR: + pixman_image_set_repeat(texture->image, PIXMAN_REPEAT_PAD); pixman_image_set_filter(texture->image, PIXMAN_FILTER_BILINEAR, NULL, 0); break; case WLR_SCALE_FILTER_NEAREST: