From 2a6fe0409d346e8c9ff3112df933c0ad4f33b32d Mon Sep 17 00:00:00 2001 From: Christopher Snowhill Date: Sun, 29 Mar 2026 23:52:38 -0700 Subject: [PATCH 1/4] chase wlroots: Add support for HDR10 output v2: Switch XRGB to XBGR v3: Rewrite HDR mode checking and setting v4: Restructure HDR support detection v5: Fix code style v6: Fix old style function declaration v7: This function should be declared static v8: This helper function can accept a const struct on input v9: Rebase now that 0.20 is merged --- include/config/rcxml.h | 12 ++++++ include/output.h | 2 + src/config/rcxml.c | 13 +++++++ src/output.c | 88 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 115 insertions(+) diff --git a/include/config/rcxml.h b/include/config/rcxml.h index 517cd907..d1841444 100644 --- a/include/config/rcxml.h +++ b/include/config/rcxml.h @@ -36,6 +36,17 @@ enum tearing_mode { LAB_TEARING_FULLSCREEN_FORCED, }; +enum hdr_mode { + LAB_HDR_DISABLED = 0, + LAB_HDR_ENABLED, +}; + +enum render_bit_depth { + LAB_RENDER_BIT_DEPTH_DEFAULT = 0, + LAB_RENDER_BIT_DEPTH_8, + LAB_RENDER_BIT_DEPTH_10, +}; + enum tiling_events_mode { LAB_TILING_EVENTS_NEVER = 0, LAB_TILING_EVENTS_REGION = 1 << 0, @@ -74,6 +85,7 @@ struct rcxml { int gap; enum adaptive_sync_mode adaptive_sync; enum tearing_mode allow_tearing; + enum hdr_mode hdr; bool auto_enable_outputs; bool reuse_output_mode; bool xwayland_persistence; diff --git a/include/output.h b/include/output.h index 89d8be04..39556596 100644 --- a/include/output.h +++ b/include/output.h @@ -71,6 +71,8 @@ struct wlr_box output_usable_area_in_layout_coords(struct output *output); void handle_output_power_manager_set_mode(struct wl_listener *listener, void *data); void output_enable_adaptive_sync(struct output *output, bool enabled); +void output_enable_hdr(struct output *output, struct wlr_output_state *os, + bool enabled); /** * Notifies whether a fullscreen view is displayed on the given output. diff --git a/src/config/rcxml.c b/src/config/rcxml.c index a3eeed63..27dc3fd7 100644 --- a/src/config/rcxml.c +++ b/src/config/rcxml.c @@ -1032,6 +1032,16 @@ set_tearing_mode(const char *str, enum tearing_mode *variable) } } +static void +set_hdr_mode(const char *str, enum hdr_mode *variable) +{ + if (parse_bool(str, -1) == 1) { + *variable = LAB_HDR_ENABLED; + } else { + *variable = LAB_HDR_DISABLED; + } +} + /* Returns true if the node's children should also be traversed */ static bool entry(xmlNode *node, char *nodename, char *content) @@ -1096,6 +1106,8 @@ entry(xmlNode *node, char *nodename, char *content) set_adaptive_sync_mode(content, &rc.adaptive_sync); } else if (!strcasecmp(nodename, "allowTearing.core")) { set_tearing_mode(content, &rc.allow_tearing); + } else if (!strcasecmp(nodename, "Hdr.core")) { + set_hdr_mode(content, &rc.hdr); } else if (!strcasecmp(nodename, "autoEnableOutputs.core")) { set_bool(content, &rc.auto_enable_outputs); } else if (!strcasecmp(nodename, "reuseOutputMode.core")) { @@ -1457,6 +1469,7 @@ rcxml_init(void) rc.gap = 0; rc.adaptive_sync = LAB_ADAPTIVE_SYNC_DISABLED; rc.allow_tearing = LAB_TEARING_DISABLED; + rc.hdr = LAB_HDR_DISABLED; rc.auto_enable_outputs = true; rc.reuse_output_mode = false; rc.xwayland_persistence = false; diff --git a/src/output.c b/src/output.c index aea35062..e207ae38 100644 --- a/src/output.c +++ b/src/output.c @@ -9,6 +9,7 @@ #define _POSIX_C_SOURCE 200809L #include "output.h" #include +#include #include #include #include @@ -51,6 +52,45 @@ #include #endif +static enum render_bit_depth +bit_depth_from_format(uint32_t render_format) +{ + if (render_format == DRM_FORMAT_XRGB2101010 || render_format == DRM_FORMAT_XBGR2101010) { + return LAB_RENDER_BIT_DEPTH_10; + } else if (render_format == DRM_FORMAT_XRGB8888 || render_format == DRM_FORMAT_ARGB8888 || + render_format == DRM_FORMAT_XBGR8888 || render_format == DRM_FORMAT_ABGR8888) { + return LAB_RENDER_BIT_DEPTH_8; + } + return LAB_RENDER_BIT_DEPTH_DEFAULT; +} + +static enum render_bit_depth +get_config_render_bit_depth(void) +{ + if (rc.hdr == LAB_HDR_ENABLED) { + return LAB_RENDER_BIT_DEPTH_10; + } + return LAB_RENDER_BIT_DEPTH_8; +} + +static void +output_state_setup_hdr(struct output *output) +{ + uint32_t render_format = output->wlr_output->render_format; + enum render_bit_depth render_bit_depth = get_config_render_bit_depth(); + if (render_bit_depth == LAB_RENDER_BIT_DEPTH_10 && + bit_depth_from_format(render_format) == render_bit_depth) { + // 10-bit was set successfully before, try to save some tests by reusing the format + wlr_output_state_set_render_format(&output->pending, render_format); + } else if (render_bit_depth == LAB_RENDER_BIT_DEPTH_10) { + wlr_output_state_set_render_format(&output->pending, DRM_FORMAT_XBGR2101010); + } else { + wlr_output_state_set_render_format(&output->pending, DRM_FORMAT_XBGR8888); + } + bool hdr = rc.hdr == LAB_HDR_ENABLED; + output_enable_hdr(output, &output->pending, hdr); +} + bool output_get_tearing_allowance(struct output *output) { @@ -371,6 +411,8 @@ configure_new_output(struct output *output) output_enable_adaptive_sync(output, true); } + output_state_setup_hdr(output); + output_state_commit(output); wlr_output_effective_resolution(wlr_output, @@ -653,6 +695,7 @@ output_config_apply(struct wlr_output_configuration_v1 *config) wlr_output_state_set_transform(os, head->state.transform); output_enable_adaptive_sync(output, head->state.adaptive_sync_enabled); + output_state_setup_hdr(output); } if (!output_state_commit(output)) { /* @@ -1136,3 +1179,48 @@ output_set_has_fullscreen_view(struct output *output, bool has_fullscreen_view) output_enable_adaptive_sync(output, has_fullscreen_view); output_state_commit(output); } + +static bool +output_supports_hdr(const struct wlr_output *output, const char **unsupported_reason_ptr) +{ + const char *unsupported_reason = NULL; + if (!(output->supported_primaries & WLR_COLOR_NAMED_PRIMARIES_BT2020)) { + unsupported_reason = "BT2020 primaries not supported by output"; + } else if (!(output->supported_transfer_functions & + WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ)) { + unsupported_reason = "PQ transfer function not supported by output"; + } else if (!server.renderer->features.output_color_transform) { + unsupported_reason = "renderer doesn't support output color transforms"; + } + if (unsupported_reason_ptr) { + *unsupported_reason_ptr = unsupported_reason; + } + return !unsupported_reason; +} + +void +output_enable_hdr(struct output *output, struct wlr_output_state *os, bool enabled) +{ + const char *unsupported_reason = NULL; + if (enabled && !output_supports_hdr(output->wlr_output, &unsupported_reason)) { + wlr_log(WLR_INFO, "Cannot enable HDR on output %s: %s", + output->wlr_output->name, unsupported_reason); + enabled = false; + } + + if (!enabled) { + if (output->wlr_output->supported_primaries != 0 || + output->wlr_output->supported_transfer_functions != 0) { + wlr_log(WLR_DEBUG, "Disabling HDR on output %s", output->wlr_output->name); + wlr_output_state_set_image_description(os, NULL); + } + return; + } + + wlr_log(WLR_DEBUG, "Enabling HDR on output %s", output->wlr_output->name); + const struct wlr_output_image_description image_desc = { + .primaries = WLR_COLOR_NAMED_PRIMARIES_BT2020, + .transfer_function = WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ, + }; + wlr_output_state_set_image_description(os, &image_desc); +} From 4b545fdcbd388d92895e7dd8d06c42d7ebdd4f1e Mon Sep 17 00:00:00 2001 From: Christopher Snowhill Date: Thu, 31 Jul 2025 22:12:08 -0700 Subject: [PATCH 2/4] chase wlroots: Add support for color-management-v1 v2: Chase wlroots!5122 v3: Chase wlroots!5141 v4: Chase wlroots!5165 v5: Chase wlroots 9b4d9eab v6: Chase wlroots 58f0867c v7: Rebase chase/0.20 v8: Reorder some of the setup code v9: Update wayland-protocols version v10: Chase wlroots 7101a69 v11: Fix code style v12: Remove server protocol declaration --- include/output.h | 3 ++- meson.build | 2 +- src/output-state.c | 5 +++++ src/output.c | 27 +++++++++++++++++---------- src/server.c | 39 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 64 insertions(+), 12 deletions(-) diff --git a/include/output.h b/include/output.h index 39556596..c17defa4 100644 --- a/include/output.h +++ b/include/output.h @@ -72,7 +72,8 @@ void handle_output_power_manager_set_mode(struct wl_listener *listener, void *data); void output_enable_adaptive_sync(struct output *output, bool enabled); void output_enable_hdr(struct output *output, struct wlr_output_state *os, - bool enabled); + bool enabled, bool silent); +void output_state_setup_hdr(struct output *output, bool silent); /** * Notifies whether a fullscreen view is displayed on the given output. diff --git a/meson.build b/meson.build index 2c2d8e57..c6c6dee9 100644 --- a/meson.build +++ b/meson.build @@ -60,7 +60,7 @@ wlroots_has_xwayland = wlroots.get_variable('have_xwayland') == 'true' have_libsfdo = not get_option('icon').disabled() wayland_server = dependency('wayland-server', version: '>=1.22.90') -wayland_protos = dependency('wayland-protocols', version: '>=1.39') +wayland_protos = dependency('wayland-protocols', version: '>=1.47') xkbcommon = dependency('xkbcommon') xcb = dependency('xcb', required: get_option('xwayland')) xcb_ewmh = dependency('xcb-ewmh', required: get_option('xwayland')) diff --git a/src/output-state.c b/src/output-state.c index 7da1108b..5be75866 100644 --- a/src/output-state.c +++ b/src/output-state.c @@ -24,6 +24,10 @@ output_state_init(struct output *output) backup_config, output->wlr_output); wlr_output_head_v1_state_apply(&backup_head->state, &output->pending); + + // This must be applied outside of config + output_state_setup_hdr(output, true); + wlr_output_configuration_v1_destroy(backup_config); } @@ -35,6 +39,7 @@ output_state_commit(struct output *output) if (committed) { wlr_output_state_finish(&output->pending); wlr_output_state_init(&output->pending); + output_state_setup_hdr(output, true); } else { wlr_log(WLR_ERROR, "Failed to commit frame"); } diff --git a/src/output.c b/src/output.c index e207ae38..f358d0f0 100644 --- a/src/output.c +++ b/src/output.c @@ -73,8 +73,8 @@ get_config_render_bit_depth(void) return LAB_RENDER_BIT_DEPTH_8; } -static void -output_state_setup_hdr(struct output *output) +void +output_state_setup_hdr(struct output *output, bool silent) { uint32_t render_format = output->wlr_output->render_format; enum render_bit_depth render_bit_depth = get_config_render_bit_depth(); @@ -88,7 +88,7 @@ output_state_setup_hdr(struct output *output) wlr_output_state_set_render_format(&output->pending, DRM_FORMAT_XBGR8888); } bool hdr = rc.hdr == LAB_HDR_ENABLED; - output_enable_hdr(output, &output->pending, hdr); + output_enable_hdr(output, &output->pending, hdr, silent); } bool @@ -411,7 +411,7 @@ configure_new_output(struct output *output) output_enable_adaptive_sync(output, true); } - output_state_setup_hdr(output); + output_state_setup_hdr(output, false); output_state_commit(output); @@ -695,7 +695,7 @@ output_config_apply(struct wlr_output_configuration_v1 *config) wlr_output_state_set_transform(os, head->state.transform); output_enable_adaptive_sync(output, head->state.adaptive_sync_enabled); - output_state_setup_hdr(output); + output_state_setup_hdr(output, false); } if (!output_state_commit(output)) { /* @@ -1199,25 +1199,32 @@ output_supports_hdr(const struct wlr_output *output, const char **unsupported_re } void -output_enable_hdr(struct output *output, struct wlr_output_state *os, bool enabled) +output_enable_hdr(struct output *output, struct wlr_output_state *os, bool enabled, bool silent) { const char *unsupported_reason = NULL; if (enabled && !output_supports_hdr(output->wlr_output, &unsupported_reason)) { - wlr_log(WLR_INFO, "Cannot enable HDR on output %s: %s", - output->wlr_output->name, unsupported_reason); + if (!silent) { + wlr_log(WLR_INFO, "Cannot enable HDR on output %s: %s", + output->wlr_output->name, unsupported_reason); + } enabled = false; } if (!enabled) { if (output->wlr_output->supported_primaries != 0 || output->wlr_output->supported_transfer_functions != 0) { - wlr_log(WLR_DEBUG, "Disabling HDR on output %s", output->wlr_output->name); + if (!silent) { + wlr_log(WLR_DEBUG, "Disabling HDR on output %s", + output->wlr_output->name); + } wlr_output_state_set_image_description(os, NULL); } return; } - wlr_log(WLR_DEBUG, "Enabling HDR on output %s", output->wlr_output->name); + if (!silent) { + wlr_log(WLR_DEBUG, "Enabling HDR on output %s", output->wlr_output->name); + } const struct wlr_output_image_description image_desc = { .primaries = WLR_COLOR_NAMED_PRIMARIES_BT2020, .transfer_function = WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ, diff --git a/src/server.c b/src/server.c index fd48efed..4f265df0 100644 --- a/src/server.c +++ b/src/server.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -724,6 +725,44 @@ server_init(void) server.tablet_manager = wlr_tablet_v2_create(server.wl_display); + if (server.renderer->features.input_color_transform) { + const enum wp_color_manager_v1_render_intent render_intents[] = { + WP_COLOR_MANAGER_V1_RENDER_INTENT_PERCEPTUAL, + }; + size_t transfer_functions_len = 0; + enum wp_color_manager_v1_transfer_function *transfer_functions = + wlr_color_manager_v1_transfer_function_list_from_renderer( + server.renderer, + &transfer_functions_len + ); + size_t primaries_len = 0; + enum wp_color_manager_v1_primaries *primaries = + wlr_color_manager_v1_primaries_list_from_renderer( + server.renderer, + &primaries_len + ); + struct wlr_color_manager_v1 *cm = wlr_color_manager_v1_create( + server.wl_display, 2, &(struct wlr_color_manager_v1_options){ + .features = { + .parametric = true, + .set_mastering_display_primaries = true, + }, + .render_intents = render_intents, + .render_intents_len = ARRAY_SIZE(render_intents), + .transfer_functions = transfer_functions, + .transfer_functions_len = transfer_functions_len, + .primaries = primaries, + .primaries_len = primaries_len, + }); + free(transfer_functions); + free(primaries); + if (!cm) { + wlr_log(WLR_ERROR, "Failed to create color manager"); + } else { + wlr_scene_set_color_manager_v1(server.scene, cm); + } + } + layers_init(); /* These get cleaned up automatically on display destroy */ From b06ebfc58001dfa09057a3b8aea1c2c458518313 Mon Sep 17 00:00:00 2001 From: Christopher Snowhill Date: Fri, 28 Nov 2025 20:34:50 -0800 Subject: [PATCH 3/4] chase wlroots: Add support for color-representation-v1 Chase wlroots!5185 v2: Add missing include --- src/server.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/server.c b/src/server.c index 4f265df0..78513aab 100644 --- a/src/server.c +++ b/src/server.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -763,6 +764,9 @@ server_init(void) } } + wlr_color_representation_manager_v1_create_with_renderer( + server.wl_display, 1, server.renderer); + layers_init(); /* These get cleaned up automatically on display destroy */ From d1727cbbc61403da3fd8ec63a3852a13a9b6c9b0 Mon Sep 17 00:00:00 2001 From: Christopher Snowhill Date: Thu, 31 Jul 2025 22:13:02 -0700 Subject: [PATCH 4/4] Document the new HDR option --- docs/labwc-config.5.scd | 12 ++++++++++++ docs/rc.xml.all | 1 + 2 files changed, 13 insertions(+) diff --git a/docs/labwc-config.5.scd b/docs/labwc-config.5.scd index 09f7b3eb..fadbcd88 100644 --- a/docs/labwc-config.5.scd +++ b/docs/labwc-config.5.scd @@ -175,6 +175,7 @@ this is for compatibility with Openbox. no no yes + no no no yes @@ -240,6 +241,17 @@ this is for compatibility with Openbox. 'pkill kanshi ; wlopm --off \*' resume 'kanshi & wlopm --on \*' ``` +** [yes|no] + Automatically enable HDR support on outputs when configuring them, + where supported by the particular output device and display. Default + is no. + + Note: Enabling this option will also currently make all non-HDR + outputs render to 10 bits per channel as well, but still sRGB. + Any configuration with `wlr-randr` or `kanshi` does not seem to + support passing this flag to my knowledge, so the only way to + override it is to enable this option. + ** [yes|no] Try to re-use the existing output mode (resolution / refresh rate). This may prevent unnecessary screenblank delays when starting labwc diff --git a/docs/rc.xml.all b/docs/rc.xml.all index bbec9d0b..09da0673 100644 --- a/docs/rc.xml.all +++ b/docs/rc.xml.all @@ -13,6 +13,7 @@ no no yes + no no no yes