diff --git a/backend/drm/util.c b/backend/drm/util.c index 57e68f33f..82aef9a36 100644 --- a/backend/drm/util.c +++ b/backend/drm/util.c @@ -105,6 +105,43 @@ 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; } + // Capture HDR static metadata whenever the EDID provides any value, even if + // the Type 1 descriptor flag is unset: some displays only advertise EOTFs + // without explicitly flagging Type 1 support, but still expose useful + // desired-content luminance values. + if (hdr_static_metadata->type1 || + hdr_static_metadata->desired_content_max_luminance > 0 || + hdr_static_metadata->desired_content_max_frame_avg_luminance > 0 || + hdr_static_metadata->desired_content_min_luminance > 0) { + 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; + wlr_log(WLR_DEBUG, + "EDID HDR static metadata: type1=%d pq=%d hlg=%d hdr_gamma=%d sdr=%d, " + "min_lum=%.4f cd/m^2, max_lum=%.1f cd/m^2, max_fall=%.1f cd/m^2", + hdr_static_metadata->type1, hdr_static_metadata->pq, + hdr_static_metadata->hlg, hdr_static_metadata->traditional_hdr, + hdr_static_metadata->traditional_sdr, + hdr_static_metadata->desired_content_min_luminance, + hdr_static_metadata->desired_content_max_luminance, + hdr_static_metadata->desired_content_max_frame_avg_luminance); + } else { + wlr_log(WLR_DEBUG, + "EDID has no HDR static metadata (sdr_only=%d)", + hdr_static_metadata->traditional_sdr); + } + 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..3c13c6872 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); } @@ -892,9 +947,20 @@ static void manager_handle_create_parametric_creator(struct wl_client *client, static void manager_handle_create_windows_scrgb(struct wl_client *client, struct wl_resource *manager_resource, uint32_t id) { - wl_resource_post_error(manager_resource, - WP_COLOR_MANAGER_V1_ERROR_UNSUPPORTED_FEATURE, - "get_windows_scrgb is not supported"); + struct wlr_color_manager_v1 *manager = manager_from_resource(manager_resource); + if (!manager->features.windows_scrgb) { + wl_resource_post_error(manager_resource, + WP_COLOR_MANAGER_V1_ERROR_UNSUPPORTED_FEATURE, + "create_windows_scrgb is not supported"); + return; + } + // Windows-scRGB: sRGB (BT.709) primaries, extended-linear transfer + // characteristic. R=G=B=1.0 corresponds to 80 cd/m². + const struct wlr_image_description_v1_data data = { + .tf_named = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR, + .primaries_named = WP_COLOR_MANAGER_V1_PRIMARIES_SRGB, + }; + image_desc_create_ready(manager, manager_resource, id, &data, false); } static const struct wp_color_manager_v1_interface manager_impl = { @@ -987,8 +1053,10 @@ struct wlr_color_manager_v1 *wlr_color_manager_v1_create(struct wl_display *disp assert(!options->features.set_primaries); assert(!options->features.set_tf_power); assert(!options->features.set_luminances); - assert(!options->features.extended_target_volume); - assert(!options->features.windows_scrgb); + // extended_target_volume requires set_mastering_display_primaries + if (options->features.extended_target_volume) { + assert(options->features.set_mastering_display_primaries); + } struct wlr_color_manager_v1 *manager = calloc(1, sizeof(*manager)); if (manager == NULL) {