From 85ab6b7eb2f4b63a858f083b6ab424560cb96982 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Wed, 25 Jan 2023 14:29:38 +0100 Subject: [PATCH 01/26] output: add wlr_output.port --- include/wlr/types/wlr_output.h | 1 + types/output/output.c | 1 + 2 files changed, 2 insertions(+) diff --git a/include/wlr/types/wlr_output.h b/include/wlr/types/wlr_output.h index e0ee7d092..0251e8627 100644 --- a/include/wlr/types/wlr_output.h +++ b/include/wlr/types/wlr_output.h @@ -159,6 +159,7 @@ struct wlr_output { char *name; char *description; // may be NULL char *make, *model, *serial; // may be NULL + char *port; // may be NULL int32_t phys_width, phys_height; // mm // Note: some backends may have zero modes diff --git a/types/output/output.c b/types/output/output.c index a352a5e59..be8c422d4 100644 --- a/types/output/output.c +++ b/types/output/output.c @@ -425,6 +425,7 @@ void wlr_output_finish(struct wlr_output *output) { free(output->make); free(output->model); free(output->serial); + free(output->port); } void wlr_output_destroy(struct wlr_output *output) { From ade7fee5d2c596aedd3124909a4a3c7934373d05 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Wed, 25 Jan 2023 14:29:59 +0100 Subject: [PATCH 02/26] backend/drm: populate wlr_output.port --- backend/drm/backend.c | 7 +++ backend/drm/drm.c | 89 ++++++++++++++++++++++++++++++++++++++ backend/drm/util.c | 41 ++++++++++++++++++ include/backend/drm/drm.h | 2 +- include/backend/drm/util.h | 3 ++ 5 files changed, 141 insertions(+), 1 deletion(-) diff --git a/backend/drm/backend.c b/backend/drm/backend.c index 9d9d5c229..d87b31f24 100644 --- a/backend/drm/backend.c +++ b/backend/drm/backend.c @@ -13,6 +13,7 @@ #include #include "backend/drm/drm.h" #include "backend/drm/fb.h" +#include "backend/drm/util.h" #include "render/drm_format_set.h" struct wlr_drm_backend *get_drm_backend_from_backend( @@ -227,6 +228,9 @@ struct wlr_backend *wlr_drm_backend_create(struct wlr_session *session, wlr_log(WLR_INFO, "Initializing DRM backend for %s (%s)", name, version->name); drmFreeVersion(version); + drmDevice *dev_info = NULL; + drmGetDevice2(dev->fd, 0, &dev_info); + struct wlr_drm_backend *drm = calloc(1, sizeof(*drm)); if (!drm) { wlr_log_errno(WLR_ERROR, "Allocation failed"); @@ -245,6 +249,9 @@ struct wlr_backend *wlr_drm_backend_create(struct wlr_session *session, drm->fd = dev->fd; drm->name = name; + drm->bus = get_drm_bus_str(dev_info); + drmFreeDevice(&dev_info); + if (parent != NULL) { drm->parent = get_drm_backend_from_backend(parent); diff --git a/backend/drm/drm.c b/backend/drm/drm.c index 6ae9dcbf3..fedc766c1 100644 --- a/backend/drm/drm.c +++ b/backend/drm/drm.c @@ -1593,6 +1593,92 @@ static drmModeModeInfo *connector_get_current_mode(struct wlr_drm_connector *wlr } } +static struct wlr_drm_connector *find_drm_connector_by_id(struct wlr_drm_backend *drm, uint32_t id) { + struct wlr_drm_connector *conn = NULL; + wl_list_for_each(conn, &drm->connectors, link) { + if (conn->id == id) { + return conn; + } + } + return NULL; +} + +static bool connector_write_root_port(struct wlr_drm_connector *wlr_conn, FILE *f) { + struct wlr_drm_backend *drm = wlr_conn->backend; + + size_t seq_num = 0; + struct wlr_drm_connector *c; + wl_list_for_each(c, &drm->connectors, link) { + seq_num++; + if (c == wlr_conn) { + break; + } + } + + return fprintf(f, "%s/connector-%zu", drm->bus, seq_num) > 0; +} + +static bool connector_write_port(struct wlr_drm_connector *wlr_conn, FILE *f); + +static bool connector_write_nested_port(struct wlr_drm_connector *wlr_conn, FILE *f) { + struct wlr_drm_backend *drm = wlr_conn->backend; + bool ok = false; + + size_t size = 0; + void *path_prop_data = get_drm_prop_blob(drm->fd, wlr_conn->id, wlr_conn->props.path, &size); + if (path_prop_data == NULL) { + goto out; + } + + uint32_t parent_conn_id = 0; + const char *child_path = NULL; + if (!parse_dp_mst_path(path_prop_data, &parent_conn_id, &child_path)) { + goto out; + } + struct wlr_drm_connector *parent = find_drm_connector_by_id(drm, parent_conn_id); + if (parent == NULL) { + goto out; + } + + if (!connector_write_port(parent, f)) { + goto out; + } + + ok = fprintf(f, "/mst-%s", child_path) > 0; + +out: + free(path_prop_data); + return ok; +} + +static bool connector_write_port(struct wlr_drm_connector *wlr_conn, FILE *f) { + if (wlr_conn->props.path != 0) { + return connector_write_nested_port(wlr_conn, f); + } else { + return connector_write_root_port(wlr_conn, f); + } +} + +static char *connector_get_port(struct wlr_drm_connector *wlr_conn) { + if (wlr_conn->backend->bus == NULL) { + return NULL; + } + + char *str = NULL; + size_t str_size = 0; + FILE *f = open_memstream(&str, &str_size); + if (f == NULL) { + return NULL; + } + + bool ok = connector_write_port(wlr_conn, f); + if (fclose(f) != 0 || !ok) { + return NULL; + } + + return str; +} + static bool connect_drm_connector(struct wlr_drm_connector *wlr_conn, const drmModeConnector *drm_conn) { struct wlr_drm_backend *drm = wlr_conn->backend; @@ -1720,6 +1806,9 @@ static bool connect_drm_connector(struct wlr_drm_connector *wlr_conn, wlr_output_set_description(output, description); free(subconnector); + + output->port = connector_get_port(wlr_conn); + wlr_conn->status = DRM_MODE_CONNECTED; return true; } diff --git a/backend/drm/util.c b/backend/drm/util.c index f30898966..393eef0f8 100644 --- a/backend/drm/util.c +++ b/backend/drm/util.c @@ -270,3 +270,44 @@ void generate_cvt_mode(drmModeModeInfo *mode, int hdisplay, int vdisplay, }; snprintf(mode->name, sizeof(mode->name), "%dx%d", hdisplay, vdisplay); } + +char *get_drm_bus_str(const drmDevice *dev) { + char buf[128]; + switch (dev->bustype) { + case DRM_BUS_PCI:; + const drmPciBusInfo *pci = dev->businfo.pci; + snprintf(buf, sizeof(buf), "pci-%04" PRIx16 ":%02" PRIx8 ":%02" PRIx8 ".%" PRIu8, + pci->domain, pci->bus, pci->dev, pci->func); + return strdup(buf); + case DRM_BUS_PLATFORM:; + const drmPlatformBusInfo *platform = dev->businfo.platform; + size_t str_size = strlen("platform-") + strlen(platform->fullname) + 1; + char *str = malloc(str_size); + if (str == NULL) { + return NULL; + } + snprintf(str, str_size, "platform-%s", platform->fullname); + return str; + } + return NULL; +} + +bool parse_dp_mst_path(const char *path, uint32_t *parent_conn_id, const char **child_path) { + const char prefix[] = "mst:"; + if (strncmp(path, prefix, strlen(prefix)) != 0) { + return false; + } + path = &path[strlen(prefix)]; + + char *id_end; + errno = 0; + unsigned long id = strtoul(path, &id_end, 10); + if (errno != 0 || id_end[0] != '-' || id > UINT32_MAX) { + wlr_log(WLR_ERROR, "Malformed PATH DP-MST property: invalid parent connector ID"); + return false; + } + + *parent_conn_id = (uint32_t)id; + *child_path = &id_end[1]; + return true; +} diff --git a/include/backend/drm/drm.h b/include/backend/drm/drm.h index 9409b7b10..2042b0df7 100644 --- a/include/backend/drm/drm.h +++ b/include/backend/drm/drm.h @@ -92,7 +92,7 @@ struct wlr_drm_backend { bool addfb2_modifiers; int fd; - char *name; + char *name, *bus; struct wlr_device *dev; struct liftoff_device *liftoff; diff --git a/include/backend/drm/util.h b/include/backend/drm/util.h index 9ba5f435e..af3eb9bff 100644 --- a/include/backend/drm/util.h +++ b/include/backend/drm/util.h @@ -38,4 +38,7 @@ void match_connectors_with_crtcs(size_t num_conns, size_t num_crtcs, const uint32_t prev_crtcs[static restrict num_crtcs], uint32_t new_crtcs[static restrict num_crtcs]); +char *get_drm_bus_str(const drmDevice *dev); +bool parse_dp_mst_path(const char *path, uint32_t *parent_conn_id, const char **child_path); + #endif From 60d72724cd740a2f9bebf56deeb645d1d4a23a30 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Tue, 30 Sep 2025 09:23:24 +0200 Subject: [PATCH 03/26] 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 04/26] 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 05/26] 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 06/26] 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 07/26] 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 08/26] 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 09/26] 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 10/26] 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 11/26] 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 12/26] 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 13/26] 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 14/26] 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 15/26] 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 16/26] 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 17/26] 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 18/26] 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 19/26] 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 20/26] 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 21/26] 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 22/26] 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 23/26] 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 24/26] 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 25/26] 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 26/26] 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; }