From c1b11c782158bc6afdf4d6c456bfe85f94db4ec6 Mon Sep 17 00:00:00 2001 From: Christopher Snowhill Date: Sun, 29 Mar 2026 23:52:38 -0700 Subject: [PATCH] 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 v10: Rewrite with multiple color format attempts v11: Add in the parts that accidentally got left in my original color-management-v1 patch v12: Add missing function prototype v13: Apply suggested changes v14: Changed HDR application setup in new output v15: Rewrite configure_new_output to use lab_wlr_scene_output_commit v16: Fixed application of HDR on external mode or output config change v17: Fixed it for real this time instead of crashing v18: Moved the effective resolution collection, plus one style change. --- include/config/rcxml.h | 7 ++ include/output.h | 3 + src/config/rcxml.c | 13 +++ src/output-state.c | 1 + src/output.c | 208 ++++++++++++++++++++++++++++++++++++++++- 5 files changed, 228 insertions(+), 4 deletions(-) diff --git a/include/config/rcxml.h b/include/config/rcxml.h index 3ef7bd67..9c2183a8 100644 --- a/include/config/rcxml.h +++ b/include/config/rcxml.h @@ -36,6 +36,12 @@ enum tearing_mode { LAB_TEARING_FULLSCREEN_FORCED, }; +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 +80,7 @@ struct rcxml { int gap; enum adaptive_sync_mode adaptive_sync; enum tearing_mode allow_tearing; + enum render_bit_depth target_render_depth; bool auto_enable_outputs; bool reuse_output_mode; uint32_t allowed_interfaces; diff --git a/include/output.h b/include/output.h index 89d8be04..c17defa4 100644 --- a/include/output.h +++ b/include/output.h @@ -71,6 +71,9 @@ 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, 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/src/config/rcxml.c b/src/config/rcxml.c index d0bb76db..0514b6c7 100644 --- a/src/config/rcxml.c +++ b/src/config/rcxml.c @@ -1080,6 +1080,16 @@ set_tearing_mode(const char *str, enum tearing_mode *variable) } } +static void +set_hdr_mode(const char *str, enum render_bit_depth *variable) +{ + if (parse_bool(str, -1) == 1) { + *variable = LAB_RENDER_BIT_DEPTH_10; + } else { + *variable = LAB_RENDER_BIT_DEPTH_8; + } +} + /* Returns true if the node's children should also be traversed */ static bool entry(xmlNode *node, char *nodename, char *content) @@ -1144,6 +1154,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.target_render_depth); } else if (!strcasecmp(nodename, "autoEnableOutputs.core")) { set_bool(content, &rc.auto_enable_outputs); } else if (!strcasecmp(nodename, "reuseOutputMode.core")) { @@ -1518,6 +1530,7 @@ rcxml_init(void) rc.gap = 0; rc.adaptive_sync = LAB_ADAPTIVE_SYNC_DISABLED; rc.allow_tearing = LAB_TEARING_DISABLED; + rc.target_render_depth = LAB_RENDER_BIT_DEPTH_DEFAULT; rc.auto_enable_outputs = true; rc.reuse_output_mode = false; rc.allowed_interfaces = UINT32_MAX; diff --git a/src/output-state.c b/src/output-state.c index 7da1108b..37ebde8a 100644 --- a/src/output-state.c +++ b/src/output-state.c @@ -24,6 +24,7 @@ output_state_init(struct output *output) backup_config, output->wlr_output); wlr_output_head_v1_state_apply(&backup_head->state, &output->pending); + wlr_output_configuration_v1_destroy(backup_config); } diff --git a/src/output.c b/src/output.c index aea35062..29a2d5a0 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,150 @@ #include #endif +static uint32_t output_formats_8bit[] = { + /* 32 bpp RGB with 8 bit component width */ + DRM_FORMAT_XRGB8888, + DRM_FORMAT_XBGR8888, + DRM_FORMAT_RGBX8888, + DRM_FORMAT_BGRX8888, + + DRM_FORMAT_ARGB8888, + DRM_FORMAT_ABGR8888, + DRM_FORMAT_RGBA8888, + DRM_FORMAT_BGRA8888, + + /* 24 bpp RGB */ + DRM_FORMAT_RGB888, + DRM_FORMAT_BGR888, +}; + +uint32_t output_formats_10bit[] = { + /* 32 bpp RGB with 10 bit component width */ + DRM_FORMAT_XRGB2101010, + DRM_FORMAT_XBGR2101010, + DRM_FORMAT_RGBX1010102, + DRM_FORMAT_BGRX1010102, + + DRM_FORMAT_ARGB2101010, + DRM_FORMAT_ABGR2101010, + DRM_FORMAT_RGBA1010102, + DRM_FORMAT_BGRA1010102, +}; + +static bool +output_set_render_format(struct output *output, uint32_t candidates[], size_t count) +{ + for (size_t i = 0; i < count; i++) { + wlr_output_state_set_render_format(&output->pending, candidates[i]); + if (wlr_output_test_state(output->wlr_output, &output->pending)) { + return true; + } + } + return false; +} + +static bool +output_format_in_candidates(uint32_t render_format, uint32_t candidates[], size_t count) +{ + for (size_t i = 0; i < count; i++) { + if (candidates[i] == render_format) { + return true; + } + } + return false; +} + +static enum render_bit_depth +bit_depth_from_format(uint32_t render_format) +{ + if (output_format_in_candidates(render_format, output_formats_10bit, + ARRAY_SIZE(output_formats_10bit))) { + return LAB_RENDER_BIT_DEPTH_10; + } else if (output_format_in_candidates(render_format, output_formats_8bit, + ARRAY_SIZE(output_formats_8bit))) { + return LAB_RENDER_BIT_DEPTH_8; + } + return LAB_RENDER_BIT_DEPTH_DEFAULT; +} + +static enum render_bit_depth +get_config_render_bit_depth(void) +{ + return rc.target_render_depth; +} + +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_state_setup_hdr(struct output *output, bool silent) +{ + uint32_t render_format = output->wlr_output->render_format; + const char *unsupported_reason = NULL; + bool hdr_supported = output_supports_hdr(output->wlr_output, + &unsupported_reason); + bool hdr_succeeded = false; + + enum render_bit_depth render_bit_depth = get_config_render_bit_depth(); + if (render_bit_depth == LAB_RENDER_BIT_DEPTH_DEFAULT) { + render_bit_depth = bit_depth_from_format(render_format); + } + + if (!hdr_supported && render_bit_depth == LAB_RENDER_BIT_DEPTH_10) { + if (!silent) { + wlr_log(WLR_INFO, "Cannot enable HDR on output %s: %s", + output->wlr_output->name, unsupported_reason); + } + render_bit_depth = LAB_RENDER_BIT_DEPTH_8; + } + + 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 + */ + hdr_succeeded = true; + } else if (render_bit_depth == LAB_RENDER_BIT_DEPTH_10) { + hdr_succeeded = output_set_render_format(output, output_formats_10bit, + ARRAY_SIZE(output_formats_10bit)); + if (!hdr_succeeded) { + if (!silent) { + wlr_log(WLR_INFO, "No 10 bit color formats" + " supported, HDR disabled."); + } + if (!output_set_render_format(output, output_formats_8bit, + ARRAY_SIZE(output_formats_8bit))) { + if (!silent) { + wlr_log(WLR_ERROR, "No 8 bit color formats" + " supported either!"); + } + } + } + } else { + if (!output_set_render_format(output, output_formats_8bit, + ARRAY_SIZE(output_formats_8bit)) && !silent) { + wlr_log(WLR_ERROR, "No 8 bit color formats supported!"); + } + } + + output_enable_hdr(output, &output->pending, hdr_succeeded, silent); +} + bool output_get_tearing_allowance(struct output *output) { @@ -371,10 +516,7 @@ configure_new_output(struct output *output) output_enable_adaptive_sync(output, true); } - output_state_commit(output); - - wlr_output_effective_resolution(wlr_output, - &output->usable_area.width, &output->usable_area.height); + output_state_setup_hdr(output, false); /* * Wait until wlr_output_layout_add_auto() returns before @@ -384,6 +526,19 @@ configure_new_output(struct output *output) server.pending_output_layout_change++; add_output_to_layout(output); server.pending_output_layout_change--; + + /* + * Commit the output this way instead, HDR needs a buffer, and + * this commit must be called after the output is added to the + * layout above. + */ + lab_wlr_scene_output_commit(output->scene_output, &output->pending); + + /* + * Collect the effective resolution after the final commit. + */ + wlr_output_effective_resolution(wlr_output, + &output->usable_area.width, &output->usable_area.height); } static uint64_t @@ -653,6 +808,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, false); } if (!output_state_commit(output)) { /* @@ -666,6 +822,19 @@ output_config_apply(struct wlr_output_configuration_v1 *config) break; } + if (output_enabled) { + /* + * The above commit was likely made without an image + * buffer attached. This will break applying HDR color + * transformation, since image descriptions must be + * committed with a buffer attached. Queue the HDR mode + * again if output is enabled, but make it silent, + * since it would have emitted messages already when + * called above. + */ + output_state_setup_hdr(output, true); + } + /* * Add or remove output from layout only if the commit went * through. Note that at startup, the output may have already @@ -1136,3 +1305,34 @@ output_set_has_fullscreen_view(struct output *output, bool has_fullscreen_view) output_enable_adaptive_sync(output, has_fullscreen_view); output_state_commit(output); } + +void +output_enable_hdr(struct output *output, struct wlr_output_state *os, + bool enabled, bool silent) +{ + if (enabled && !output_supports_hdr(output->wlr_output, NULL)) { + enabled = false; + } + + if (!enabled) { + if (output->wlr_output->supported_primaries != 0 || + output->wlr_output->supported_transfer_functions != 0) { + if (!silent) { + wlr_log(WLR_DEBUG, "Disabling HDR on output %s", + output->wlr_output->name); + } + wlr_output_state_set_image_description(os, NULL); + } + return; + } + + 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, + }; + wlr_output_state_set_image_description(os, &image_desc); +}