From 78830ab696a1baddb06a470e2cb97b04d2cb0bdc Mon Sep 17 00:00:00 2001 From: Christopher Snowhill Date: Wed, 13 May 2026 21:00:56 -0700 Subject: [PATCH] output: expose EDID HDR static metadata Read desired_content_{min,max,max_frame_avg}_luminance from the EDID HDR static metadata block in the DRM backend and expose them on wlr_output via a new wlr_output_hdr_metadata struct, mirroring the existing default_primaries pattern. In wlr_color_management_v1, propagate mastering display primaries, mastering luminance, max_cll and max_fall from the output's image description into the image_description_v1_data, falling back to the EDID-derived values when the compositor hasn't populated them. Send the protocol's target_primaries / target_luminance / target_max_cll / target_max_fall from that data, resolving two TODOs. Co-Authored-By: Claude Opus 4.7 (1M context) --- backend/drm/util.c | 17 ++++++++ include/wlr/types/wlr_output.h | 19 +++++++++ types/wlr_color_management_v1.c | 73 +++++++++++++++++++++++++++++---- 3 files changed, 100 insertions(+), 9 deletions(-) diff --git a/backend/drm/util.c b/backend/drm/util.c index 57e68f33f..600466e56 100644 --- a/backend/drm/util.c +++ b/backend/drm/util.c @@ -105,6 +105,23 @@ void parse_edid(struct wlr_drm_connector *conn, size_t len, const uint8_t *data) output->supported_transfer_functions |= WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ; } + if (hdr_static_metadata->type1) { + output->hdr_metadata_value = (struct wlr_output_hdr_metadata){ + .desired_content_min_luminance = hdr_static_metadata->desired_content_min_luminance, + }; + if (hdr_static_metadata->desired_content_max_luminance > 0) { + output->hdr_metadata_value.has_desired_content_max_luminance = true; + output->hdr_metadata_value.desired_content_max_luminance = + hdr_static_metadata->desired_content_max_luminance; + } + if (hdr_static_metadata->desired_content_max_frame_avg_luminance > 0) { + output->hdr_metadata_value.has_desired_content_max_frame_average_luminance = true; + output->hdr_metadata_value.desired_content_max_frame_average_luminance = + hdr_static_metadata->desired_content_max_frame_avg_luminance; + } + output->hdr_metadata = &output->hdr_metadata_value; + } + di_info_destroy(info); } diff --git a/include/wlr/types/wlr_output.h b/include/wlr/types/wlr_output.h index c8e44b0e6..b7f874b9c 100644 --- a/include/wlr/types/wlr_output.h +++ b/include/wlr/types/wlr_output.h @@ -85,6 +85,22 @@ enum wlr_output_state_mode_type { WLR_OUTPUT_STATE_MODE_CUSTOM, }; +/** + * HDR static metadata block 1 values, as advertised by the display via EDID. + * + * Luminances are given in cd/m². desired_content_min_luminance is always + * available (defaults to 0 when the EDID block does not provide a value). + * desired_content_max_luminance and desired_content_max_frame_average_luminance + * are optional and only meaningful when their corresponding has_* flag is set. + */ +struct wlr_output_hdr_metadata { + double desired_content_min_luminance; + bool has_desired_content_max_luminance; + double desired_content_max_luminance; + bool has_desired_content_max_frame_average_luminance; + double desired_content_max_frame_average_luminance; +}; + /** * Colorimetric image description. * @@ -194,6 +210,8 @@ struct wlr_output { char *make, *model, *serial; // may be NULL int32_t phys_width, phys_height; // mm const struct wlr_color_primaries *default_primaries; // may be NULL + // HDR static metadata block 1 values from the EDID, NULL if not advertised + const struct wlr_output_hdr_metadata *hdr_metadata; // Note: some backends may have zero modes struct wl_list modes; // wlr_output_mode.link @@ -278,6 +296,7 @@ struct wlr_output { struct wlr_output_image_description image_description_value; struct wlr_color_transform *color_transform; struct wlr_color_primaries default_primaries_value; + struct wlr_output_hdr_metadata hdr_metadata_value; } WLR_PRIVATE; }; diff --git a/types/wlr_color_management_v1.c b/types/wlr_color_management_v1.c index 27f9d1829..cef360c5b 100644 --- a/types/wlr_color_management_v1.c +++ b/types/wlr_color_management_v1.c @@ -160,16 +160,31 @@ static void image_desc_handle_get_information(struct wl_client *client, wp_image_description_info_v1_send_luminances(resource, round(luminances.min * 10000), round(luminances.max), round(luminances.reference)); - // TODO: send mastering display primaries and luminances here when we add - // support for features.set_mastering_display_primaries + const struct wlr_color_primaries *target_primaries = &primaries; + if (image_desc->data.has_mastering_display_primaries) { + target_primaries = &image_desc->data.mastering_display_primaries; + } wp_image_description_info_v1_send_target_primaries(resource, - encode_cie1931_coord(primaries.red.x), encode_cie1931_coord(primaries.red.y), - encode_cie1931_coord(primaries.green.x), encode_cie1931_coord(primaries.green.y), - encode_cie1931_coord(primaries.blue.x), encode_cie1931_coord(primaries.blue.y), - encode_cie1931_coord(primaries.white.x), encode_cie1931_coord(primaries.white.y)); + encode_cie1931_coord(target_primaries->red.x), encode_cie1931_coord(target_primaries->red.y), + encode_cie1931_coord(target_primaries->green.x), encode_cie1931_coord(target_primaries->green.y), + encode_cie1931_coord(target_primaries->blue.x), encode_cie1931_coord(target_primaries->blue.y), + encode_cie1931_coord(target_primaries->white.x), encode_cie1931_coord(target_primaries->white.y)); + double target_min = luminances.min; + double target_max = luminances.max; + if (image_desc->data.has_mastering_luminance) { + target_min = image_desc->data.mastering_luminance.min; + target_max = image_desc->data.mastering_luminance.max; + } wp_image_description_info_v1_send_target_luminance(resource, - round(luminances.min * 10000), round(luminances.max)); - // TODO: send target_max_cll and target_max_fall + round(target_min * 10000), round(target_max)); + if (image_desc->data.max_cll > 0) { + wp_image_description_info_v1_send_target_max_cll(resource, + image_desc->data.max_cll); + } + if (image_desc->data.max_fall > 0) { + wp_image_description_info_v1_send_target_max_fall(resource, + image_desc->data.max_fall); + } wp_image_description_info_v1_send_done(resource); wl_resource_destroy(resource); } @@ -266,10 +281,50 @@ static void cm_output_handle_get_image_description(struct wl_client *client, .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; + const struct wlr_output *output = cm_output->output; + const struct wlr_output_image_description *image_desc = output->image_description; if (image_desc != NULL) { data.tf_named = wlr_color_manager_v1_transfer_function_from_wlr(image_desc->transfer_function); data.primaries_named = wlr_color_manager_v1_primaries_from_wlr(image_desc->primaries); + if (image_desc->mastering_luminance.max > 0) { + data.has_mastering_luminance = true; + data.mastering_luminance.min = image_desc->mastering_luminance.min; + data.mastering_luminance.max = image_desc->mastering_luminance.max; + } + if (image_desc->max_cll > 0) { + data.max_cll = (uint32_t)round(image_desc->max_cll); + } + if (image_desc->max_fall > 0) { + data.max_fall = (uint32_t)round(image_desc->max_fall); + } + const struct wlr_color_primaries *p = &image_desc->mastering_display_primaries; + if (p->red.x != 0 || p->red.y != 0 || p->green.x != 0 || p->green.y != 0 || + p->blue.x != 0 || p->blue.y != 0 || p->white.x != 0 || p->white.y != 0) { + data.has_mastering_display_primaries = true; + data.mastering_display_primaries = *p; + } + } + // Fall back to EDID-derived target metadata when the compositor didn't + // populate it on the output's image description. + if (!data.has_mastering_luminance && output->hdr_metadata != NULL) { + const struct wlr_output_hdr_metadata *hdr = output->hdr_metadata; + if (hdr->has_desired_content_max_luminance) { + data.has_mastering_luminance = true; + data.mastering_luminance.min = hdr->desired_content_min_luminance; + data.mastering_luminance.max = hdr->desired_content_max_luminance; + } + } + if (data.max_cll == 0 && output->hdr_metadata != NULL && + output->hdr_metadata->has_desired_content_max_luminance) { + data.max_cll = (uint32_t)round(output->hdr_metadata->desired_content_max_luminance); + } + if (data.max_fall == 0 && output->hdr_metadata != NULL && + output->hdr_metadata->has_desired_content_max_frame_average_luminance) { + data.max_fall = (uint32_t)round(output->hdr_metadata->desired_content_max_frame_average_luminance); + } + if (!data.has_mastering_display_primaries && output->default_primaries != NULL) { + data.has_mastering_display_primaries = true; + data.mastering_display_primaries = *output->default_primaries; } image_desc_create_ready(cm_output->manager, cm_output_resource, id, &data, true); }