From 04dc72e8c194ff05213e8a12ebd59c1d1b2f294a Mon Sep 17 00:00:00 2001 From: Kirill Primak Date: Sun, 4 Aug 2024 17:24:22 +0300 Subject: [PATCH 01/77] util: introduce rectangle packing helper --- include/wlr/util/rectpack.h | 48 ++++ util/meson.build | 1 + util/rectpack.c | 533 ++++++++++++++++++++++++++++++++++++ 3 files changed, 582 insertions(+) create mode 100644 include/wlr/util/rectpack.h create mode 100644 util/rectpack.c diff --git a/include/wlr/util/rectpack.h b/include/wlr/util/rectpack.h new file mode 100644 index 000000000..a3525d981 --- /dev/null +++ b/include/wlr/util/rectpack.h @@ -0,0 +1,48 @@ +/* + * 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_UTIL_RECTPACK_H +#define WLR_UTIL_RECTPACK_H + +#include +#include + +#include +#include + +struct wlr_layer_surface_v1; + +struct wlr_rectpack_rules { + // If true, the corresponding side will be stretched to take all available area + bool grow_width, grow_height; +}; + +/** + * Place a rectangle within bounds so that it doesn't intersect with the + * exclusive region. + * + * exclusive may be NULL. + * + * Returns false if there's not enough space or on memory allocation error. + */ +bool wlr_rectpack_place(const struct wlr_box *bounds, pixman_region32_t *exclusive, + const struct wlr_box *box, struct wlr_rectpack_rules *rules, struct wlr_box *out); + +/** + * Place a struct wlr_layer_surface_v1 within bounds so that it doesn't + * intersect with the exclusive region. If the layer surface has exclusive zone, + * the corresponding area will be added to the exclusive region. + * + * Returns false if there's not enough space or on memory allocation error, in + * which case the exclusive region is left intact. + */ +bool wlr_rectpack_place_wlr_layer_surface_v1(const struct wlr_box *bounds, + pixman_region32_t *exclusive, struct wlr_layer_surface_v1 *surface, struct wlr_box *out); + + +#endif diff --git a/util/meson.build b/util/meson.build index 053e2c5eb..b9cd66313 100644 --- a/util/meson.build +++ b/util/meson.build @@ -6,6 +6,7 @@ wlr_files += files( 'global.c', 'log.c', 'rect_union.c', + 'rectpack.c', 'region.c', 'set.c', 'shm.c', diff --git a/util/rectpack.c b/util/rectpack.c new file mode 100644 index 000000000..41359d685 --- /dev/null +++ b/util/rectpack.c @@ -0,0 +1,533 @@ +#include +#include +#include + +#include +#include +#include + +struct wlr_rectpack_bandbuf { + pixman_box32_t *data; + size_t len; + size_t cap; +}; + +static void bandbuf_init(struct wlr_rectpack_bandbuf *buf) { + *buf = (struct wlr_rectpack_bandbuf){0}; +} + +static void bandbuf_finish(struct wlr_rectpack_bandbuf *buf) { + free(buf->data); +} + +static bool bandbuf_add(struct wlr_rectpack_bandbuf *buf, pixman_box32_t *band) { + if (buf->len == buf->cap) { + buf->cap = buf->cap == 0 ? 32 : buf->cap * 2; + pixman_box32_t *data = realloc(buf->data, sizeof(*data) * buf->cap); + if (data == NULL) { + return false; + } + buf->data = data; + } + buf->data[buf->len++] = *band; + return true; +} + +static bool lines_overlap(int a1, int b1, int a2, int b2) { + int max_a = a1 > a2 ? a1 : a2; + int min_b = b1 < b2 ? b1 : b2; + return min_b > max_a; +} + +// Returns false if the constraint overlaps with the origin +static bool line_crop(int *a, int *b, int exclusive_a, int exclusive_b, + int origin_a, int origin_b) { + if (exclusive_a >= origin_b) { + if (*b > exclusive_a) { + *b = exclusive_a; + } + } else if (exclusive_b <= origin_a) { + if (*a < exclusive_b) { + *a = exclusive_b; + } + } else { + return false; + } + return true; +} + +// Returns false on memory allocation error +static bool grow_2d(const struct wlr_box *bounds, pixman_region32_t *exclusive, + pixman_box32_t *target) { + // The goal is to find the largest empty rectangle within the exclusive region such that it + // would contain the target rectangle. To achieve this, we split the remaining empty space into + // horizontal bands in such a way that they form two trapezoids (top and bottom), and then + // iterate over pairs of bands from each trapezoid to find the largest rectangle. + + // Note: Pixman regions are stored as sorted "y-x-banded" arrays of rectangles. For + // implementation details, see pixman-region.c. + + int n_exclusive_rects; + pixman_box32_t *exclusive_rects = pixman_region32_rectangles(exclusive, &n_exclusive_rects); + + // Step 1: find the middle band, split the exclusive region in 3 subregions: + // - above the target; + // - vertically overlapping with the target; + // - below the target. + + // The widest band, contains the target + pixman_box32_t mid_band = (pixman_box32_t){ + .x1 = bounds->x, + .y1 = bounds->y, + .x2 = bounds->x + bounds->width, + .y2 = bounds->y + bounds->height, + }; + + // Find exclusive rectangles which are above the target, crop the middle band from the top + int above_rect_i = 0; + for (; above_rect_i < n_exclusive_rects; above_rect_i++) { + pixman_box32_t *rect = &exclusive_rects[above_rect_i]; + if (rect->y2 > target->y1) { + break; + } + mid_band.y1 = rect->y2; + } + + // Find exclusive rectangles which vertically overlap with the target, crop the middle band from + // the other sides + int below_rect_i = above_rect_i--; + for (; below_rect_i < n_exclusive_rects; below_rect_i++) { + pixman_box32_t *rect = &exclusive_rects[below_rect_i]; + if (rect->y1 >= target->y2) { + mid_band.y2 = rect->y1; + break; + } + + // Invariant: no exclusive rectangle overlaps with the minimum box + line_crop(&mid_band.x1, &mid_band.x2, rect->x1, rect->x2, target->x1, target->x2); + } + + // The rest of the exclusive rectangles are below the target + + // Step 2: find the rest of the bands. + + bool ok = false; + + struct wlr_rectpack_bandbuf bandbuf; + bandbuf_init(&bandbuf); + + + // Find all "above" bands, moving up from the middle + // Note: this includes the middle band itself + if (!bandbuf_add(&bandbuf, &mid_band)) { + goto end; + } + + while (above_rect_i >= 0) { + pixman_box32_t *rect = &exclusive_rects[above_rect_i]; + pixman_box32_t *last = &bandbuf.data[bandbuf.len - 1]; + + // Invariant: a band farther from the middle one is horizontally contained by a band closer + // to the middle one + pixman_box32_t band = { + .x1 = last->x1, + .y1 = rect->y1, + .x2 = last->x2, + .y2 = rect->y2, + }; + // Extend the last one up in case of free vertical space + last->y1 = band.y2; + + // Process the x-band of exclusive rectangles + do { + if (!line_crop(&band.x1, &band.x2, rect->x1, rect->x2, target->x1, target->x2)) { + // A rectangle is horizontally overlapping with the target; it's not possible to go + // further + goto above_done; + } else if (above_rect_i-- == 0) { + // All rectangles processed + break; + } + rect = &exclusive_rects[above_rect_i]; + } while (rect->y1 == band.y1); + + if (band.x1 == last->x1 && band.x2 == last->x2) { + // Horizontally equal to the last; extend that up instead + last->y1 = band.y1; + } else { + if (!bandbuf_add(&bandbuf, &band)) { + goto end; + } + } + } + // Extend the last one up in case of free vertical space + bandbuf.data[bandbuf.len - 1].y1 = bounds->y; +above_done:; + + size_t split_i = bandbuf.len; + + // Find all "below" bands, moving down from the middle + // Same logic applies + + if (!bandbuf_add(&bandbuf, &mid_band)) { + goto end; + } + + while (below_rect_i < n_exclusive_rects) { + pixman_box32_t *rect = &exclusive_rects[below_rect_i]; + pixman_box32_t *last = &bandbuf.data[bandbuf.len - 1]; + + pixman_box32_t band = { + .x1 = last->x1, + .y1 = rect->y1, + .x2 = last->x2, + .y2 = rect->y2, + }; + last->y2 = band.y1; + + do { + if (!line_crop(&band.x1, &band.x2, rect->x1, rect->x2, target->x1, target->x2)) { + goto below_done; + } else if (++below_rect_i == n_exclusive_rects) { + break; + } + rect = &exclusive_rects[below_rect_i]; + } while (rect->y1 == band.y1); + + if (band.x1 == last->x1 && band.x2 == last->x2) { + last->y2 = band.y2; + } else { + if (!bandbuf_add(&bandbuf, &band)) { + goto end; + } + } + } + bandbuf.data[bandbuf.len - 1].y2 = bounds->y + bounds->height; +below_done:; + + // Step 3: find the largest rectangle within the empty bands. Between rectangles with the same + // area, pick the one that uses the smaller bounds space better; i.e. pick a "more vertical" + // rectangle within horizontal bounds and vice versa. + + bool bounds_horizontal = bounds->width > bounds->height; + int best_area = (target->x2 - target->x1) * (target->y2 - target->y1); + + // Note: the (mid_band, mid_band) pair is checked too + for (size_t above_i = 0; above_i < split_i; above_i++) { + pixman_box32_t *above = &bandbuf.data[above_i]; + for (size_t below_i = split_i; below_i < bandbuf.len; below_i++) { + pixman_box32_t *below = &bandbuf.data[below_i]; + + pixman_box32_t curr = { + .x1 = above->x1 > below->x1 ? above->x1 : below->x1, + .y1 = above->y1, + .x2 = above->x2 < below->x2 ? above->x2 : below->x2, + .y2 = below->y2, + }; + + int width = curr.x2 - curr.x1, height = curr.y2 - curr.y1; + int area = width * height; + if (area > best_area || (area == best_area && bounds_horizontal != (width > height))) { + *target = curr; + best_area = area; + } + } + } + + ok = true; + +end: + bandbuf_finish(&bandbuf); + return ok; +} + +bool wlr_rectpack_place(const struct wlr_box *bounds, pixman_region32_t *exclusive, + const struct wlr_box *box, struct wlr_rectpack_rules *rules, struct wlr_box *out) { + assert(!wlr_box_empty(box)); + + if (bounds->width < box->width || bounds->height < box->height) { + // Trivial case: the bounds are not big enough for the minimum box + return false; + } + + int n_exclusive_rects = 0; + pixman_box32_t *exclusive_rects = NULL; + if (exclusive != NULL) { + exclusive_rects = pixman_region32_rectangles(exclusive, &n_exclusive_rects); + } + + if (n_exclusive_rects == 0) { + // Trivial case: the exclusive region is empty or ignored, just stretch to bounds as needed + if (rules->grow_width) { + out->x = bounds->x; + out->width = bounds->width; + } else { + out->x = box->x; + out->width = box->width; + } + if (rules->grow_height) { + out->y = bounds->y; + out->height = bounds->height; + } else { + out->y = box->y; + out->height = box->height; + } + return true; + } + + // Step 1: fit the minimum box within the exclusive region. + + // Instead of trying to fit a min_width×min_height rectangle, shrink the available region and + // try to fit a 1×1 rectangle. + int dwidth = box->width - 1; + int dheight = box->height - 1; + + pixman_box32_t shrunk_bounds = { + .x1 = bounds->x, + .y1 = bounds->y, + .x2 = bounds->x + bounds->width - dwidth, + .y2 = bounds->y + bounds->height - dheight, + }; + + pixman_region32_t available; + pixman_region32_init(&available); + + if (dwidth != 0 || dheight != 0) { + pixman_box32_t *expanded_rects = calloc(n_exclusive_rects, sizeof(*expanded_rects)); + if (expanded_rects == NULL) { + wlr_log(WLR_ERROR, "Allocation failed"); + pixman_region32_fini(&available); + return false; + } + + for (int i = 0; i < n_exclusive_rects; i++) { + pixman_box32_t *rect = &exclusive_rects[i]; + expanded_rects[i] = (pixman_box32_t){ + .x1 = rect->x1 - dwidth, + .y1 = rect->y1 - dheight, + .x2 = rect->x2, + .y2 = rect->y2, + }; + } + + pixman_region32_t expanded; + pixman_region32_init_rects(&expanded, expanded_rects, n_exclusive_rects); + pixman_region32_inverse(&available, &expanded, &shrunk_bounds); + pixman_region32_fini(&expanded); + + free(expanded_rects); + } else { + // Fast path: the minimum box is already 1×1 + pixman_region32_inverse(&available, exclusive, &shrunk_bounds); + } + + int n_available_rects; + pixman_box32_t *available_rects = pixman_region32_rectangles(&available, &n_available_rects); + if (n_available_rects == 0) { + // Not enough free space within the exclusive region for the minimum box + pixman_region32_fini(&available); + return false; + } + + // Find the position closest to the desired one + int best_x = box->x, best_y = box->y; + int best_dist_sq = INT_MAX; + for (int i = 0; i < n_available_rects; i++) { + pixman_box32_t *rect = &available_rects[i]; + int clamped_x = box->x < rect->x1 ? rect->x1 : + box->x >= rect->x2 ? rect->x2 - 1 : box->x; + int clamped_y = box->y < rect->y1 ? rect->y1 : + box->y >= rect->y2 ? rect->y2 - 1 : box->y; + + int dx = clamped_x - box->x, dy = clamped_y - box->y; + int dist_sq = dx * dx + dy * dy; + if (dist_sq < best_dist_sq) { + best_dist_sq = dist_sq; + best_x = clamped_x; + best_y = clamped_y; + } + + if (best_dist_sq == 0) { + break; + } + } + pixman_region32_fini(&available); + + // Step 2: grow the box as requested. + + pixman_box32_t result = { + .x1 = best_x, + .y1 = best_y, + .x2 = best_x + box->width, + .y2 = best_y + box->height, + }; + + if (rules->grow_width && rules->grow_height) { + if (!grow_2d(bounds, exclusive, &result)) { + return false; + } + } else if (rules->grow_width) { + // Stretch and then crop + int o1 = result.x1, o2 = result.x2; + result.x1 = bounds->x; + result.x2 = bounds->x + bounds->width; + + for (int i = 0; i < n_exclusive_rects; i++) { + pixman_box32_t *rect = &exclusive_rects[i]; + if (lines_overlap(result.y1, result.y2, rect->y1, rect->y2)) { + // Invariant: no exclusive rectangle overlaps with the minimum box + line_crop(&result.x1, &result.x2, rect->x1, rect->x2, o1, o2); + } + } + } else if (rules->grow_height) { + // Same as width + int o1 = result.y1, o2 = result.y2; + result.y1 = bounds->y; + result.y2 = bounds->y + bounds->height; + + for (int i = 0; i < n_exclusive_rects; i++) { + pixman_box32_t *rect = &exclusive_rects[i]; + if (lines_overlap(result.x1, result.x2, rect->x1, rect->x2)) { + // Invariant: no exclusive rectangle overlaps with the minimum box + line_crop(&result.y1, &result.y2, rect->y1, rect->y2, o1, o2); + } + } + } + + *out = (struct wlr_box){ + .x = result.x1, + .y = result.y1, + .width = result.x2 - result.x1, + .height = result.y2 - result.y1, + }; + return true; +} + +bool wlr_rectpack_place_wlr_layer_surface_v1(const struct wlr_box *bounds, + pixman_region32_t *exclusive, struct wlr_layer_surface_v1 *surface, struct wlr_box *out) { + struct wlr_layer_surface_v1_state *state = &surface->current; + uint32_t anchor = state->anchor; + + int m_top = anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP ? state->margin.top : 0; + int m_bottom = anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM ? state->margin.bottom : 0; + int m_left = anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT ? state->margin.left : 0; + int m_right = anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT ? state->margin.right : 0; + + int m_horiz = m_left + m_right; + int m_verti = m_top + m_bottom; + + enum wlr_edges exclusive_edge = wlr_layer_surface_v1_get_exclusive_edge(surface); + int full_exclusive_zone = state->exclusive_zone; + + switch (exclusive_edge) { + case WLR_EDGE_LEFT: + full_exclusive_zone += m_left; + break; + case WLR_EDGE_RIGHT: + full_exclusive_zone += m_right; + break; + case WLR_EDGE_TOP: + full_exclusive_zone += m_top; + break; + case WLR_EDGE_BOTTOM: + full_exclusive_zone += m_bottom; + break; + case WLR_EDGE_NONE: + break; + } + + int desired_width = (int)state->desired_width, desired_height = (int)state->desired_height; + bool grow_width = desired_width == 0, grow_height = desired_height == 0; + + int min_width = (grow_width ? 1 : desired_width) + m_horiz; + int min_height = (grow_height ? 1 : desired_height) + m_verti; + + if (min_width < 1) { + min_width = 1; + } + if (min_height < 1) { + min_height = 1; + } + + switch (exclusive_edge) { + case WLR_EDGE_LEFT: + case WLR_EDGE_RIGHT: + if (min_width < full_exclusive_zone) { + min_width = full_exclusive_zone; + } + break; + case WLR_EDGE_TOP: + case WLR_EDGE_BOTTOM: + if (min_height < full_exclusive_zone) { + min_height = full_exclusive_zone; + } + break; + case WLR_EDGE_NONE: + break; + } + + uint32_t edges = anchor; + if ((edges & (WLR_EDGE_LEFT | WLR_EDGE_RIGHT)) == (WLR_EDGE_LEFT | WLR_EDGE_RIGHT)) { + edges &= ~(WLR_EDGE_LEFT | WLR_EDGE_RIGHT); + } + if ((edges & (WLR_EDGE_TOP | WLR_EDGE_BOTTOM)) == (WLR_EDGE_TOP | WLR_EDGE_BOTTOM)) { + edges &= ~(WLR_EDGE_TOP | WLR_EDGE_BOTTOM); + } + + struct wlr_box box = { + .x = bounds->x, + .y = bounds->y, + .width = min_width, + .height = min_height, + }; + + if ((anchor & (WLR_EDGE_LEFT | WLR_EDGE_RIGHT)) == WLR_EDGE_RIGHT) { + box.x += bounds->width - box.width; + } else if ((anchor & (WLR_EDGE_LEFT | WLR_EDGE_RIGHT)) != WLR_EDGE_LEFT) { + box.x += bounds->width / 2 - box.width / 2; + } + if ((anchor & (WLR_EDGE_TOP | WLR_EDGE_BOTTOM)) == WLR_EDGE_BOTTOM) { + box.y += bounds->height - box.height; + } else if ((anchor & (WLR_EDGE_TOP | WLR_EDGE_BOTTOM)) != WLR_EDGE_TOP) { + box.y += bounds->height / 2 - box.height / 2; + } + + struct wlr_rectpack_rules rules = { + .grow_width = grow_width, + .grow_height = grow_height, + }; + + if (!wlr_rectpack_place(bounds, state->exclusive_zone >= 0 ? exclusive : NULL, + &box, &rules, out)) { + return false; + } + + if (exclusive_edge != WLR_EDGE_NONE) { + struct wlr_box exclusive_box = *out; + switch (exclusive_edge) { + case WLR_EDGE_RIGHT: + exclusive_box.x += out->width - full_exclusive_zone; + // Fallthrough + case WLR_EDGE_LEFT: + exclusive_box.width = full_exclusive_zone; + break; + case WLR_EDGE_BOTTOM: + exclusive_box.y += out->height - full_exclusive_zone; + // Fallthrough + case WLR_EDGE_TOP: + exclusive_box.height = full_exclusive_zone; + break; + case WLR_EDGE_NONE: + abort(); // Unreachable + } + + struct wlr_box intersection; + if (wlr_box_intersection(&intersection, &exclusive_box, bounds)) { + pixman_region32_union_rect(exclusive, exclusive, intersection.x, + intersection.y, (unsigned int)intersection.width, + (unsigned int)intersection.height); + } + } + + return true; +} From ccec4116b3a113e9a082f9712f675c30552a0b35 Mon Sep 17 00:00:00 2001 From: Yixue Wang Date: Sun, 20 Jul 2025 23:01:58 +0800 Subject: [PATCH 02/77] 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 03/77] 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 04/77] 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 05/77] 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 06/77] 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 07/77] 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 08/77] 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 09/77] 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 10/77] 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 11/77] 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 12/77] 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 13/77] 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 14/77] 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 15/77] 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 16/77] 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 17/77] 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 18/77] 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 19/77] 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 20/77] 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 21/77] 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 22/77] 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 23/77] 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 24/77] 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 25/77] 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 26/77] 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 27/77] 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 28/77] 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 29/77] 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 30/77] 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 31/77] 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 32/77] 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 33/77] 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 34/77] 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 35/77] 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 36/77] 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 37/77] 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 38/77] 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 39/77] 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 40/77] 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 41/77] 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 42/77] 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 43/77] 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 44/77] 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 45/77] 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 46/77] 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 47/77] 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 48/77] 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 49/77] 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 50/77] 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 51/77] 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 52/77] 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 53/77] 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 54/77] 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 55/77] 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 56/77] 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 57/77] 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 58/77] 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 59/77] 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 60/77] 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 61/77] 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 62/77] 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 63/77] 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 64/77] 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 65/77] 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 66/77] 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 67/77] 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 68/77] 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 69/77] 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 70/77] 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 71/77] 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 72/77] 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 73/77] 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 74/77] 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 75/77] 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 76/77] 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 77/77] 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; }