From b8545889e997e6fefb9e43ac16fd4f04bbb8c3a5 Mon Sep 17 00:00:00 2001 From: Christopher Snowhill Date: Sat, 16 May 2026 18:41:58 -0700 Subject: [PATCH 1/5] color-management: Read EDID HDR metadata and complete the protocol The compositor parsed EDID HDR static metadata but never used it: HDR outputs always used fixed PQ defaults (max 10000 nits), so clients could not discover the real display capabilities. Three color-management protocol features were also stubbed with unsupported_feature errors. Plumb the EDID HDR luminance and the panel chromaticity into the BT2100 output color state as luminance and mastering display metadata, and implement the missing protocol features: - set_mastering_display_primaries and set_mastering_luminance - extended target volume (a reference luminance above the maximum luminance is no longer rejected) - create_windows_scrgb ClutterColorState gains a ClutterColorMasteringMetadata describing the color volume the content was authored for; its mastering luminance feeds the existing tone mapping. The image description info now reports the real target primaries, target luminance and target max CLL/FALL. This lets clients such as Wine's Wayland driver both discover an output's HDR capabilities and attach mastering display metadata when presenting HDR content. Assisted-By: Claude Opus 4.7 --- clutter/clutter/clutter-color-state-params.c | 110 ++++++++- clutter/clutter/clutter-color-state-params.h | 29 +++ src/backends/meta-color-device.c | 92 +++++++- src/tests/edid-tests.c | 9 + src/tests/wayland-color-management-test.c | 46 ++++ .../wayland-test-clients/color-management.c | 102 ++++++++ src/wayland/meta-wayland-color-management.c | 218 +++++++++++++++--- 7 files changed, 560 insertions(+), 46 deletions(-) diff --git a/clutter/clutter/clutter-color-state-params.c b/clutter/clutter/clutter-color-state-params.c index e85b3db91d0..93d4e4c6308 100644 --- a/clutter/clutter/clutter-color-state-params.c +++ b/clutter/clutter/clutter-color-state-params.c @@ -58,6 +58,7 @@ typedef struct _ClutterColorStateParams ClutterColorimetry colorimetry; ClutterEOTF eotf; ClutterLuminance luminance; + ClutterColorMasteringMetadata mastering; } ClutterColorStateParams; G_DEFINE_TYPE (ClutterColorStateParams, @@ -163,6 +164,15 @@ clutter_color_state_params_get_luminance (ClutterColorStateParams *color_state_p } } +const ClutterColorMasteringMetadata * +clutter_color_state_params_get_mastering_metadata (ClutterColorStateParams *color_state_params) +{ + g_return_val_if_fail (CLUTTER_IS_COLOR_STATE_PARAMS (color_state_params), + NULL); + + return &color_state_params->mastering; +} + static const ClutterLuminance sdr_default_luminance = { .type = CLUTTER_LUMINANCE_TYPE_DERIVED, .min = 0.2f, @@ -548,6 +558,40 @@ luminances_equal (const ClutterLuminance *lum, luminance_value_approx_equal (lum->ref, other_lum->ref, 0.1f); } +static gboolean +mastering_metadata_equal (const ClutterColorMasteringMetadata *mastering, + const ClutterColorMasteringMetadata *other_mastering) +{ + if (mastering->has_primaries != other_mastering->has_primaries || + mastering->has_luminance != other_mastering->has_luminance || + mastering->has_max_cll != other_mastering->has_max_cll || + mastering->has_max_fall != other_mastering->has_max_fall) + return FALSE; + + if (mastering->has_primaries && + !primaries_equal (&mastering->primaries, &other_mastering->primaries)) + return FALSE; + + if (mastering->has_luminance && + (!luminance_value_approx_equal (mastering->min_lum, + other_mastering->min_lum, 0.1f) || + !luminance_value_approx_equal (mastering->max_lum, + other_mastering->max_lum, 0.1f))) + return FALSE; + + if (mastering->has_max_cll && + !luminance_value_approx_equal (mastering->max_cll, + other_mastering->max_cll, 0.1f)) + return FALSE; + + if (mastering->has_max_fall && + !luminance_value_approx_equal (mastering->max_fall, + other_mastering->max_fall, 0.1f)) + return FALSE; + + return TRUE; +} + static guint get_eotf_key (ClutterEOTF eotf) { @@ -2093,7 +2137,11 @@ clutter_color_state_params_equals (ClutterColorState *color_state, target_lum = clutter_color_state_params_get_luminance (other_color_state_params); - return luminances_equal (lum, target_lum); + if (!luminances_equal (lum, target_lum)) + return FALSE; + + return mastering_metadata_equal (&color_state_params->mastering, + &other_color_state_params->mastering); } static gboolean @@ -2125,7 +2173,9 @@ clutter_color_state_params_to_string (ClutterColorState *color_state) ClutterColorStateParams *color_state_params = CLUTTER_COLOR_STATE_PARAMS (color_state); g_autofree char *primaries_name = NULL; + g_autofree char *mastering_str = NULL; const char *transfer_function_name; + const ClutterColorMasteringMetadata *mastering; const ClutterLuminance *lum; uint64_t id; @@ -2133,20 +2183,33 @@ clutter_color_state_params_to_string (ClutterColorState *color_state) primaries_name = clutter_colorimetry_to_string (color_state_params->colorimetry); transfer_function_name = clutter_eotf_to_string (color_state_params->eotf); lum = clutter_color_state_params_get_luminance (color_state_params); + mastering = &color_state_params->mastering; + + if (mastering->has_primaries || mastering->has_luminance || + mastering->has_max_cll || mastering->has_max_fall) + { + mastering_str = + g_strdup_printf (", mastering: (primaries: %s, min lum: %f, " + "max lum: %f, max CLL: %f, max FALL: %f)", + mastering->has_primaries ? "set" : "unset", + mastering->min_lum, + mastering->max_lum, + mastering->max_cll, + mastering->max_fall); + } return g_strdup_printf ("ClutterColorState %" G_GUINT64_FORMAT " " "(primaries: %s, transfer function: %s, " "min lum: %f, max lum: %f, ref lum: %f, " - "mastering max lum: %f)", + "mastering max lum: %f%s)", id, primaries_name, transfer_function_name, lum->min, lum->max, lum->ref, - lum->mastering_max); - - + lum->mastering_max, + mastering_str ? mastering_str : ""); } static ClutterEncodingRequiredFormat @@ -2407,6 +2470,43 @@ clutter_color_state_params_new_from_primitives (ClutterContext *context, luminance.mastering_max); } +/** + * clutter_color_state_params_new_with_mastering: + * + * Like clutter_color_state_params_new_from_primitives(), but additionally + * attaches optional mastering display metadata describing the color volume + * the content was authored for. + * + * Return value: A new ClutterColorState object. + **/ +ClutterColorState * +clutter_color_state_params_new_with_mastering (ClutterContext *context, + ClutterColorimetry colorimetry, + ClutterEOTF eotf, + ClutterLuminance luminance, + const ClutterColorMasteringMetadata *mastering) +{ + ClutterColorState *color_state; + ClutterColorStateParams *color_state_params; + + if (mastering && mastering->has_luminance && mastering->max_lum > 0.0f && + luminance.type == CLUTTER_LUMINANCE_TYPE_EXPLICIT) + luminance.mastering_max = mastering->max_lum; + + color_state = clutter_color_state_params_new_from_primitives (context, + colorimetry, + eotf, + luminance); + + if (mastering) + { + color_state_params = CLUTTER_COLOR_STATE_PARAMS (color_state); + color_state_params->mastering = *mastering; + } + + return color_state; +} + static gboolean cicp_primaries_to_clutter (ClutterCicpPrimaries primaries, ClutterColorimetry *colorimetry, diff --git a/clutter/clutter/clutter-color-state-params.h b/clutter/clutter/clutter-color-state-params.h index bec38dec842..e33128241eb 100644 --- a/clutter/clutter/clutter-color-state-params.h +++ b/clutter/clutter/clutter-color-state-params.h @@ -100,6 +100,25 @@ typedef struct _ClutterLuminance float mastering_max; } ClutterLuminance; +/* + * Optional mastering display metadata, describing the color volume the + * content was actually authored for. Unlike the container colorimetry and + * luminance, these values are hints and may exceed the container volume + * (extended target volume). + */ +typedef struct _ClutterColorMasteringMetadata +{ + gboolean has_primaries; + ClutterPrimaries primaries; + gboolean has_luminance; + float min_lum; + float max_lum; + gboolean has_max_cll; + float max_cll; + gboolean has_max_fall; + float max_fall; +} ClutterColorMasteringMetadata; + typedef enum { CLUTTER_CICP_PRIMARIES_SRGB = 1, @@ -160,6 +179,13 @@ ClutterColorState * clutter_color_state_params_new_from_primitives (ClutterConte ClutterEOTF eotf, ClutterLuminance luminance); +CLUTTER_EXPORT +ClutterColorState * clutter_color_state_params_new_with_mastering (ClutterContext *context, + ClutterColorimetry colorimetry, + ClutterEOTF eotf, + ClutterLuminance luminance, + const ClutterColorMasteringMetadata *mastering); + CLUTTER_EXPORT ClutterColorState * clutter_color_state_params_new_from_cicp (ClutterContext *context, const ClutterCicp *cicp, @@ -174,6 +200,9 @@ const ClutterEOTF * clutter_color_state_params_get_eotf (ClutterColorStateParams CLUTTER_EXPORT const ClutterLuminance * clutter_color_state_params_get_luminance (ClutterColorStateParams *color_state_params); +CLUTTER_EXPORT +const ClutterColorMasteringMetadata * clutter_color_state_params_get_mastering_metadata (ClutterColorStateParams *color_state_params); + CLUTTER_EXPORT const ClutterLuminance * clutter_eotf_get_default_luminance (ClutterEOTF eotf); diff --git a/src/backends/meta-color-device.c b/src/backends/meta-color-device.c index a20e0784b03..f0572caa3b3 100644 --- a/src/backends/meta-color-device.c +++ b/src/backends/meta-color-device.c @@ -684,16 +684,78 @@ on_manager_ready (MetaColorManager *color_manager, create_cd_device (color_device); } +/* Reference white luminance used for PQ HDR content, matching the PQ + * default luminance in clutter-color-state-params.c. */ +#define HDR_REFERENCE_LUMINANCE 203.0f + static void -get_color_metadata_from_monitor (MetaMonitor *monitor, - ClutterColorimetry *colorimetry, - ClutterEOTF *eotf) +fill_hdr_metadata_from_edid (const MetaEdidInfo *edid_info, + ClutterLuminance *luminance, + ClutterColorMasteringMetadata *mastering) +{ + const struct di_hdr_static_metadata *hdr = &edid_info->hdr_static_metadata; + const struct di_color_primaries *primaries = + &edid_info->default_color_primaries; + float max_lum = (float) hdr->desired_content_max_luminance; + float min_lum = (float) hdr->desired_content_min_luminance; + float max_fall = (float) hdr->desired_content_max_frame_avg_luminance; + + if (max_lum > 0.0f) + { + /* For PQ the encoded signal range is fixed; the display's actual + * capability is carried as the mastering luminance. */ + luminance->type = CLUTTER_LUMINANCE_TYPE_EXPLICIT; + luminance->min = min_lum; + luminance->max = max_lum; + luminance->ref = HDR_REFERENCE_LUMINANCE; + luminance->mastering_max = max_lum; + + mastering->has_luminance = TRUE; + mastering->min_lum = min_lum; + mastering->max_lum = max_lum; + + /* Report the display's peak luminance as the target max content light + * level too. Clients (e.g. Wine's Wayland driver) read target_max_cll + * to derive the peak brightness they advertise to applications. */ + mastering->has_max_cll = TRUE; + mastering->max_cll = max_lum; + } + + if (primaries->has_primaries) + { + mastering->has_primaries = TRUE; + mastering->primaries.r_x = primaries->primary[0].x; + mastering->primaries.r_y = primaries->primary[0].y; + mastering->primaries.g_x = primaries->primary[1].x; + mastering->primaries.g_y = primaries->primary[1].y; + mastering->primaries.b_x = primaries->primary[2].x; + mastering->primaries.b_y = primaries->primary[2].y; + mastering->primaries.w_x = primaries->default_white.x; + mastering->primaries.w_y = primaries->default_white.y; + } + + if (max_fall > 0.0f) + { + mastering->has_max_fall = TRUE; + mastering->max_fall = max_fall; + } +} + +static void +get_color_metadata_from_monitor (MetaMonitor *monitor, + ClutterColorimetry *colorimetry, + ClutterEOTF *eotf, + ClutterLuminance *luminance, + ClutterColorMasteringMetadata *mastering) { MetaOutput *output; const MetaOutputInfo *output_info; MetaEdidInfo *edid_info; const struct di_color_primaries *primaries; + *luminance = (ClutterLuminance) { .type = CLUTTER_LUMINANCE_TYPE_DERIVED }; + *mastering = (ClutterColorMasteringMetadata) { 0 }; + switch (meta_monitor_get_color_mode (monitor)) { case META_COLOR_MODE_DEFAULT: @@ -707,6 +769,12 @@ get_color_metadata_from_monitor (MetaMonitor *monitor, colorimetry->colorspace = CLUTTER_COLORSPACE_BT2020; eotf->type = CLUTTER_EOTF_TYPE_NAMED; eotf->tf_name = CLUTTER_TRANSFER_FUNCTION_PQ; + + output = meta_monitor_get_main_output (monitor); + output_info = meta_output_get_info (output); + edid_info = output_info->edid_info; + if (edid_info) + fill_hdr_metadata_from_edid (edid_info, luminance, mastering); return; case META_COLOR_MODE_SDR_NATIVE: output = meta_monitor_get_main_output (monitor); @@ -754,9 +822,11 @@ update_color_state (MetaColorDevice *color_device) ClutterColorimetry colorimetry; ClutterEOTF eotf; ClutterLuminance luminance; + ClutterColorMasteringMetadata mastering; UpdateResult result = 0; - get_color_metadata_from_monitor (monitor, &colorimetry, &eotf); + get_color_metadata_from_monitor (monitor, &colorimetry, &eotf, + &luminance, &mastering); if (meta_debug_control_is_hdr_forced (debug_control)) { @@ -764,15 +834,19 @@ update_color_state (MetaColorDevice *color_device) colorimetry.colorspace = CLUTTER_COLORSPACE_BT2020; eotf.type = CLUTTER_EOTF_TYPE_NAMED; eotf.tf_name = CLUTTER_TRANSFER_FUNCTION_PQ; + luminance = (ClutterLuminance) { .type = CLUTTER_LUMINANCE_TYPE_DERIVED }; + mastering = (ClutterColorMasteringMetadata) { 0 }; } - luminance = *clutter_eotf_get_default_luminance (eotf); + if (luminance.type == CLUTTER_LUMINANCE_TYPE_DERIVED) + luminance = *clutter_eotf_get_default_luminance (eotf); luminance.ref = luminance.ref * color_device->reference_luminance_factor; - color_state = clutter_color_state_params_new_from_primitives (clutter_context, - colorimetry, - eotf, - luminance); + color_state = clutter_color_state_params_new_with_mastering (clutter_context, + colorimetry, + eotf, + luminance, + &mastering); if (!color_device->color_state || !clutter_color_state_equals (color_device->color_state, color_state)) diff --git a/src/tests/edid-tests.c b/src/tests/edid-tests.c index dbf4dde16d3..fe3224b6636 100644 --- a/src/tests/edid-tests.c +++ b/src/tests/edid-tests.c @@ -78,4 +78,13 @@ main (int argc, g_assert_true (edid_info->hdr_static_metadata.pq); g_assert_true (edid_info->colorimetry.bt2020_rgb); g_assert_true (edid_info->colorimetry.bt2020_ycc); + + /* The default color primaries feed the HDR mastering display metadata + * reported to clients via the color-management protocol. */ + g_assert_true (edid_info->default_color_primaries.has_primaries); + g_assert_true (edid_info->default_color_primaries.has_default_white_point); + g_assert_cmpfloat (edid_info->default_color_primaries.primary[0].x, >, 0.0f); + g_assert_cmpfloat (edid_info->default_color_primaries.primary[0].y, >, 0.0f); + g_assert_cmpfloat (edid_info->hdr_static_metadata.desired_content_min_luminance, + >=, 0.0f); } diff --git a/src/tests/wayland-color-management-test.c b/src/tests/wayland-color-management-test.c index f1ee183640e..39b8664ae9e 100644 --- a/src/tests/wayland-color-management-test.c +++ b/src/tests/wayland-color-management-test.c @@ -78,6 +78,7 @@ color_management (void) const ClutterColorimetry *colorimetry; const ClutterEOTF *eotf; const ClutterLuminance *lum; + const ClutterColorMasteringMetadata *mastering; const MtkAnonymousFile *file; wayland_test_client = meta_wayland_test_client_new (test_context, @@ -161,6 +162,51 @@ color_management (void) g_assert_nonnull (file); emit_sync_event (4); + wait_for_sync_point (5); + color_state = get_window_color_state (test_window); + color_state_params = CLUTTER_COLOR_STATE_PARAMS (color_state); + colorimetry = clutter_color_state_params_get_colorimetry (color_state_params); + g_assert_cmpuint (colorimetry->type, ==, CLUTTER_COLORIMETRY_TYPE_COLORSPACE); + g_assert_cmpuint (colorimetry->colorspace, ==, CLUTTER_COLORSPACE_BT2020); + mastering = clutter_color_state_params_get_mastering_metadata (color_state_params); + g_assert_true (mastering->has_primaries); + g_assert_cmpfloat_with_epsilon (mastering->primaries.r_x, 0.64f, TEST_COLOR_EPSILON); + g_assert_cmpfloat_with_epsilon (mastering->primaries.r_y, 0.33f, TEST_COLOR_EPSILON); + g_assert_cmpfloat_with_epsilon (mastering->primaries.g_x, 0.30f, TEST_COLOR_EPSILON); + g_assert_cmpfloat_with_epsilon (mastering->primaries.g_y, 0.60f, TEST_COLOR_EPSILON); + g_assert_cmpfloat_with_epsilon (mastering->primaries.b_x, 0.15f, TEST_COLOR_EPSILON); + g_assert_cmpfloat_with_epsilon (mastering->primaries.b_y, 0.06f, TEST_COLOR_EPSILON); + g_assert_cmpfloat_with_epsilon (mastering->primaries.w_x, 0.34567f, TEST_COLOR_EPSILON); + g_assert_cmpfloat_with_epsilon (mastering->primaries.w_y, 0.35850f, TEST_COLOR_EPSILON); + g_assert_true (mastering->has_luminance); + g_assert_cmpfloat_with_epsilon (mastering->min_lum, 0.005f, TEST_COLOR_EPSILON); + g_assert_cmpfloat (mastering->max_lum, ==, 1000.0f); + g_assert_true (mastering->has_max_cll); + g_assert_cmpfloat (mastering->max_cll, ==, 1000.0f); + g_assert_true (mastering->has_max_fall); + g_assert_cmpfloat (mastering->max_fall, ==, 400.0f); + lum = clutter_color_state_params_get_luminance (color_state_params); + g_assert_cmpfloat (lum->mastering_max, ==, 1000.0f); + emit_sync_event (5); + + wait_for_sync_point (6); + color_state = get_window_color_state (test_window); + color_state_params = CLUTTER_COLOR_STATE_PARAMS (color_state); + colorimetry = clutter_color_state_params_get_colorimetry (color_state_params); + g_assert_cmpuint (colorimetry->type, ==, CLUTTER_COLORIMETRY_TYPE_COLORSPACE); + g_assert_cmpuint (colorimetry->colorspace, ==, CLUTTER_COLORSPACE_SRGB); + eotf = clutter_color_state_params_get_eotf (color_state_params); + g_assert_cmpuint (eotf->type, ==, CLUTTER_EOTF_TYPE_NAMED); + g_assert_cmpuint (eotf->tf_name, ==, CLUTTER_TRANSFER_FUNCTION_LINEAR); + lum = clutter_color_state_params_get_luminance (color_state_params); + g_assert_cmpuint (lum->type, ==, CLUTTER_LUMINANCE_TYPE_EXPLICIT); + g_assert_cmpfloat_with_epsilon (lum->min, 0.0f, TEST_COLOR_EPSILON); + g_assert_cmpfloat_with_epsilon (lum->max, 80.0f, TEST_COLOR_EPSILON); + g_assert_cmpfloat_with_epsilon (lum->ref, 203.0f, TEST_COLOR_EPSILON); + mastering = clutter_color_state_params_get_mastering_metadata (color_state_params); + g_assert_true (mastering->has_primaries); + emit_sync_event (6); + meta_wayland_test_client_finish (wayland_test_client); } diff --git a/src/tests/wayland-test-clients/color-management.c b/src/tests/wayland-test-clients/color-management.c index 98f76e9d51a..8d3282fbb53 100644 --- a/src/tests/wayland-test-clients/color-management.c +++ b/src/tests/wayland-test-clients/color-management.c @@ -252,6 +252,82 @@ create_image_description_from_icc (WaylandDisplay *display, g_assert_cmpint (image_description_context.image_description_id, >, 0); } +static void +create_image_description_with_mastering (WaylandDisplay *display, + struct wp_image_description_v1 **image_description) +{ + struct wp_image_description_creator_params_v1 *creator_params; + ImageDescriptionContext image_description_context; + + creator_params = + wp_color_manager_v1_create_parametric_creator (display->color_management_mgr); + + wp_image_description_creator_params_v1_set_primaries_named ( + creator_params, + WP_COLOR_MANAGER_V1_PRIMARIES_BT2020); + wp_image_description_creator_params_v1_set_tf_named ( + creator_params, + WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST2084_PQ); + wp_image_description_creator_params_v1_set_luminances ( + creator_params, + float_to_scaled_uint32 (0.005f), + 10000, + 203); + wp_image_description_creator_params_v1_set_mastering_display_primaries ( + creator_params, + float_to_scaled_uint32_chromaticity (custom_primaries.r_x), + float_to_scaled_uint32_chromaticity (custom_primaries.r_y), + float_to_scaled_uint32_chromaticity (custom_primaries.g_x), + float_to_scaled_uint32_chromaticity (custom_primaries.g_y), + float_to_scaled_uint32_chromaticity (custom_primaries.b_x), + float_to_scaled_uint32_chromaticity (custom_primaries.b_y), + float_to_scaled_uint32_chromaticity (custom_primaries.w_x), + float_to_scaled_uint32_chromaticity (custom_primaries.w_y)); + wp_image_description_creator_params_v1_set_mastering_luminance ( + creator_params, + float_to_scaled_uint32 (0.005f), + 1000); + wp_image_description_creator_params_v1_set_max_cll (creator_params, 1000); + wp_image_description_creator_params_v1_set_max_fall (creator_params, 400); + + image_description_context.image_description_id = 0; + image_description_context.creation_failed = FALSE; + + *image_description = + wp_image_description_creator_params_v1_create (creator_params); + wp_image_description_v1_add_listener ( + *image_description, + &image_description_listener, + &image_description_context); + + wait_for_image_description_ready (&image_description_context, display); + + g_assert_false (image_description_context.creation_failed); + g_assert_cmpint (image_description_context.image_description_id, >, 0); +} + +static void +create_image_description_windows_scrgb (WaylandDisplay *display, + struct wp_image_description_v1 **image_description) +{ + ImageDescriptionContext image_description_context; + + image_description_context.image_description_id = 0; + image_description_context.creation_failed = FALSE; + + *image_description = + wp_color_manager_v1_create_windows_scrgb (display->color_management_mgr); + wp_image_description_v1_add_listener ( + *image_description, + &image_description_listener, + &image_description_context); + + wait_for_image_description_ready (&image_description_context, display); + + g_assert_false (image_description_context.creation_failed); + g_assert_cmpint (image_description_context.image_description_id, >, 0); +} + int main (int argc, char **argv) @@ -363,6 +439,32 @@ main (int argc, test_driver_sync_point (display->test_driver, 4, NULL); wait_for_sync_event (display, 4); + create_image_description_with_mastering (display, &image_description); + wp_color_management_surface_v1_set_image_description ( + color_surface, + image_description, + WP_COLOR_MANAGER_V1_RENDER_INTENT_PERCEPTUAL); + + wl_surface_commit (surface); + + wp_image_description_v1_destroy (image_description); + + test_driver_sync_point (display->test_driver, 5, NULL); + wait_for_sync_event (display, 5); + + create_image_description_windows_scrgb (display, &image_description); + wp_color_management_surface_v1_set_image_description ( + color_surface, + image_description, + WP_COLOR_MANAGER_V1_RENDER_INTENT_PERCEPTUAL); + + wl_surface_commit (surface); + + wp_image_description_v1_destroy (image_description); + + test_driver_sync_point (display->test_driver, 6, NULL); + wait_for_sync_event (display, 6); + wp_color_management_surface_v1_destroy (color_surface); return EXIT_SUCCESS; diff --git a/src/wayland/meta-wayland-color-management.c b/src/wayland/meta-wayland-color-management.c index d542654a2f1..8d596c00dde 100644 --- a/src/wayland/meta-wayland-color-management.c +++ b/src/wayland/meta-wayland-color-management.c @@ -131,6 +131,7 @@ typedef struct _MetaWaylandCreatorParams ClutterColorimetry colorimetry; ClutterEOTF eotf; ClutterLuminance lum; + ClutterColorMasteringMetadata mastering; gboolean is_colorimetry_set; gboolean is_eotf_set; @@ -539,6 +540,12 @@ send_primaries (struct wl_resource *resource, float_to_scaled_uint32_chromaticity (primaries->b_y), float_to_scaled_uint32_chromaticity (primaries->w_x), float_to_scaled_uint32_chromaticity (primaries->w_y)); +} + +static void +send_target_primaries (struct wl_resource *resource, + const ClutterPrimaries *primaries) +{ wp_image_description_info_v1_send_target_primaries ( resource, float_to_scaled_uint32_chromaticity (primaries->r_x), @@ -559,7 +566,8 @@ send_information_from_params (struct wl_resource *info_resource, enum wp_color_manager_v1_transfer_function tf; ClutterColorStateParams *color_state_params; const ClutterColorimetry *colorimetry; - const ClutterPrimaries *primaries; + const ClutterPrimaries *container_primaries = NULL; + const ClutterColorMasteringMetadata *mastering; const ClutterEOTF *eotf; const ClutterLuminance *lum; @@ -573,14 +581,16 @@ send_information_from_params (struct wl_resource *info_resource, wp_image_description_info_v1_send_primaries_named (info_resource, primaries_named); - primaries = clutter_colorspace_to_primaries (colorimetry->colorspace); - send_primaries (info_resource, primaries); + container_primaries = + clutter_colorspace_to_primaries (colorimetry->colorspace); break; case CLUTTER_COLORIMETRY_TYPE_PRIMARIES: - send_primaries (info_resource, colorimetry->primaries); + container_primaries = colorimetry->primaries; break; } + send_primaries (info_resource, container_primaries); + eotf = clutter_color_state_params_get_eotf (color_state_params); switch (eotf->type) { @@ -603,9 +613,34 @@ send_information_from_params (struct wl_resource *info_resource, float_to_scaled_uint32 (lum->min), (uint32_t) lum->max, (uint32_t) lum->ref); - wp_image_description_info_v1_send_target_luminance (info_resource, - float_to_scaled_uint32 (lum->min), - (uint32_t) lum->max); + + mastering = clutter_color_state_params_get_mastering_metadata (color_state_params); + + /* The target color volume: the mastering display metadata when known, + * otherwise the container color volume itself. */ + if (mastering->has_primaries) + send_target_primaries (info_resource, &mastering->primaries); + else + send_target_primaries (info_resource, container_primaries); + + if (mastering->has_luminance && mastering->max_lum > 0.0f) + wp_image_description_info_v1_send_target_luminance ( + info_resource, + float_to_scaled_uint32 (mastering->min_lum), + (uint32_t) mastering->max_lum); + else + wp_image_description_info_v1_send_target_luminance ( + info_resource, + float_to_scaled_uint32 (lum->min), + (uint32_t) lum->max); + + if (mastering->has_max_cll && mastering->max_cll > 0.0f) + wp_image_description_info_v1_send_target_max_cll (info_resource, + (uint32_t) mastering->max_cll); + + if (mastering->has_max_fall && mastering->max_fall > 0.0f) + wp_image_description_info_v1_send_target_max_fall (info_resource, + (uint32_t) mastering->max_fall); } static void @@ -1310,10 +1345,11 @@ creator_params_create (struct wl_client *client, id); color_state = - clutter_color_state_params_new_from_primitives (clutter_context, - creator_params->colorimetry, - creator_params->eotf, - creator_params->lum); + clutter_color_state_params_new_with_mastering (clutter_context, + creator_params->colorimetry, + creator_params->eotf, + creator_params->lum, + &creator_params->mastering); image_desc = meta_wayland_image_description_new_color_state (color_manager, @@ -1518,14 +1554,8 @@ creator_params_set_luminance (struct wl_client *client, return; } - if (ref > max) - { - wl_resource_post_error (resource, - WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_INVALID_LUMINANCE, - "The reference luminance is bigger than the maximum luminance, " - "extended target volume unsupported"); - return; - } + /* A reference luminance above the maximum luminance is allowed: it + * indicates an extended target volume, which we advertise support for. */ creator_params->lum.type = CLUTTER_LUMINANCE_TYPE_EXPLICIT; creator_params->lum.min = min; @@ -1546,9 +1576,28 @@ creator_params_set_mastering_display_primaries (struct wl_client *client, int32_t w_x, int32_t w_y) { - wl_resource_post_error (resource, - WP_COLOR_MANAGER_V1_ERROR_UNSUPPORTED_FEATURE, - "Setting mastering display primaries is not supported"); + MetaWaylandCreatorParams *creator_params = + wl_resource_get_user_data (resource); + ClutterPrimaries *primaries = &creator_params->mastering.primaries; + + if (creator_params->mastering.has_primaries) + { + wl_resource_post_error (resource, + WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_ALREADY_SET, + "The mastering display primaries were already set"); + return; + } + + primaries->r_x = scaled_uint32_to_float_chromaticity (r_x); + primaries->r_y = scaled_uint32_to_float_chromaticity (r_y); + primaries->g_x = scaled_uint32_to_float_chromaticity (g_x); + primaries->g_y = scaled_uint32_to_float_chromaticity (g_y); + primaries->b_x = scaled_uint32_to_float_chromaticity (b_x); + primaries->b_y = scaled_uint32_to_float_chromaticity (b_y); + primaries->w_x = scaled_uint32_to_float_chromaticity (w_x); + primaries->w_y = scaled_uint32_to_float_chromaticity (w_y); + + creator_params->mastering.has_primaries = TRUE; } static void @@ -1557,9 +1606,33 @@ creator_params_set_mastering_luminance (struct wl_client *client, uint32_t min_lum, uint32_t max_lum) { - wl_resource_post_error (resource, - WP_COLOR_MANAGER_V1_ERROR_UNSUPPORTED_FEATURE, - "Setting mastering display luminances is not supported"); + MetaWaylandCreatorParams *creator_params = + wl_resource_get_user_data (resource); + float min, max; + + if (creator_params->mastering.has_luminance) + { + wl_resource_post_error (resource, + WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_ALREADY_SET, + "The mastering display luminances were already set"); + return; + } + + min = scaled_uint32_to_float (min_lum); + max = (float) max_lum; + + if (max > 0.0f && max <= min) + { + wl_resource_post_error (resource, + WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_INVALID_LUMINANCE, + "The mastering maximum luminance is smaller than " + "the minimum luminance"); + return; + } + + creator_params->mastering.has_luminance = TRUE; + creator_params->mastering.min_lum = min; + creator_params->mastering.max_lum = max; } static void @@ -1567,8 +1640,19 @@ creator_params_set_max_cll (struct wl_client *client, struct wl_resource *resource, uint32_t max_cll) { - /* ignoring for now */ - /* FIXME: technically we must send errors in some cases */ + MetaWaylandCreatorParams *creator_params = + wl_resource_get_user_data (resource); + + if (creator_params->mastering.has_max_cll) + { + wl_resource_post_error (resource, + WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_ALREADY_SET, + "The maximum content light level was already set"); + return; + } + + creator_params->mastering.has_max_cll = TRUE; + creator_params->mastering.max_cll = (float) max_cll; } static void @@ -1576,8 +1660,19 @@ creator_params_set_max_fall (struct wl_client *client, struct wl_resource *resource, uint32_t max_fall) { - /* ignoring for now */ - /* FIXME: technically we must send errors in some cases */ + MetaWaylandCreatorParams *creator_params = + wl_resource_get_user_data (resource); + + if (creator_params->mastering.has_max_fall) + { + wl_resource_post_error (resource, + WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_ALREADY_SET, + "The maximum frame-average light level was already set"); + return; + } + + creator_params->mastering.has_max_fall = TRUE; + creator_params->mastering.max_fall = (float) max_fall; } static const struct wp_image_description_creator_params_v1_interface @@ -1840,9 +1935,62 @@ color_manager_create_windows_scrgb (struct wl_client *client, struct wl_resource *resource, uint32_t id) { - wl_resource_post_error (resource, - WP_COLOR_MANAGER_V1_ERROR_UNSUPPORTED_FEATURE, - "Windows scRGB is not supported"); + MetaWaylandColorManager *color_manager = wl_resource_get_user_data (resource); + ClutterContext *clutter_context = get_clutter_context (color_manager); + struct wl_resource *image_desc_resource; + g_autoptr (ClutterColorState) color_state = NULL; + MetaWaylandImageDescription *image_desc; + ClutterColorimetry colorimetry; + ClutterEOTF eotf; + ClutterLuminance luminance; + ClutterColorMasteringMetadata mastering; + + /* Windows-scRGB: sRGB (BT.709) primaries with an extended linear transfer + * characteristic. R=G=B=1.0 maps to 80 cd/m². The mastering color volume + * may extend up to BT.2100. */ + colorimetry = (ClutterColorimetry) { + .type = CLUTTER_COLORIMETRY_TYPE_COLORSPACE, + .colorspace = CLUTTER_COLORSPACE_SRGB, + }; + eotf = (ClutterEOTF) { + .type = CLUTTER_EOTF_TYPE_NAMED, + .tf_name = CLUTTER_TRANSFER_FUNCTION_LINEAR, + }; + luminance = (ClutterLuminance) { + .type = CLUTTER_LUMINANCE_TYPE_EXPLICIT, + .min = 0.0f, + .max = 80.0f, + .ref = 203.0f, + .mastering_max = 80.0f, + }; + mastering = (ClutterColorMasteringMetadata) { + .has_primaries = TRUE, + .primaries = *clutter_colorspace_to_primaries (CLUTTER_COLORSPACE_BT2020), + }; + + color_state = + clutter_color_state_params_new_with_mastering (clutter_context, + colorimetry, + eotf, + luminance, + &mastering); + + image_desc_resource = + wl_resource_create (client, + &wp_image_description_v1_interface, + wl_resource_get_version (resource), + id); + + image_desc = + meta_wayland_image_description_new_color_state (color_manager, + image_desc_resource, + color_state, + META_WAYLAND_IMAGE_DESCRIPTION_FLAGS_DEFAULT); + + wl_resource_set_implementation (image_desc_resource, + &meta_wayland_image_description_interface, + image_desc, + image_description_destructor); } static void @@ -1871,6 +2019,12 @@ color_manager_send_supported_events (struct wl_resource *resource) WP_COLOR_MANAGER_V1_FEATURE_SET_TF_POWER); wp_color_manager_v1_send_supported_feature (resource, WP_COLOR_MANAGER_V1_FEATURE_SET_LUMINANCES); + wp_color_manager_v1_send_supported_feature (resource, + WP_COLOR_MANAGER_V1_FEATURE_SET_MASTERING_DISPLAY_PRIMARIES); + wp_color_manager_v1_send_supported_feature (resource, + WP_COLOR_MANAGER_V1_FEATURE_EXTENDED_TARGET_VOLUME); + wp_color_manager_v1_send_supported_feature (resource, + WP_COLOR_MANAGER_V1_FEATURE_WINDOWS_SCRGB); wp_color_manager_v1_send_supported_tf_named (resource, WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA22); wp_color_manager_v1_send_supported_tf_named (resource, -- GitLab From 43a34d8807c56c7ec877b9ed1b8a803f2efd8835 Mon Sep 17 00:00:00 2001 From: Christopher Snowhill Date: Wed, 27 May 2026 00:41:29 -0700 Subject: [PATCH 2/5] color-management: Tone map HDR content to detected display capabilities MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous commit plumbed EDID HDR metadata into the output color state but the tone mapper still short-circuited for HDR targets, so PQ content mastered for 10000 nits was handed un-tonemapped to displays that can only do a fraction of that and clipped at the panel peak. Per-frame mastering metadata supplied through the wp_color_management_v1 protocol was likewise ignored when computing the tone curve. Plumb the new ClutterColorMasteringMetadata into needs_tone_mapping(), update_tone_mapping_uniforms() and the CPU mirror, picking the effective mastering peak from max_cll, then max_lum, falling back to the container's nominal mastering_max. This enables HDR->HDR tone mapping whenever the source mastering peak exceeds the display peak, and uses content mastering metadata (when present) instead of the nominal PQ ceiling so the modified-Reinhard curve compresses against the real content range. Also fix fill_hdr_metadata_from_edid() to seed the container's ClutterLuminance from the PQ defaults instead of overwriting max with the EDID peak. The display's capability now lives only in mastering_max and the mastering struct, matching the original comment's intent and keeping pq_inv_eotf()'s 1.0 = 10000 cd/m² semantics intact so the encoding chain round-trips correctly. Assisted-By: Claude Opus 4.7 --- clutter/clutter/clutter-color-state-params.c | 187 +++++++++++++------ src/backends/meta-color-device.c | 15 +- 2 files changed, 145 insertions(+), 57 deletions(-) diff --git a/clutter/clutter/clutter-color-state-params.c b/clutter/clutter/clutter-color-state-params.c index 93d4e4c6308..e63e5e43d7f 100644 --- a/clutter/clutter/clutter-color-state-params.c +++ b/clutter/clutter/clutter-color-state-params.c @@ -604,23 +604,47 @@ get_eotf_key (ClutterEOTF eotf) } } +/* Best estimate of the peak luminance to use as the mastering ceiling for + * tone mapping. Prefers explicit mastering metadata when the client (or EDID + * parser) provided it, falling back to the container's nominal mastering max + * (e.g. 10000 nits for PQ, 80 nits for sRGB). */ +static float +get_effective_mastering_max (const ClutterLuminance *lum, + const ClutterColorMasteringMetadata *mastering) +{ + if (mastering) + { + if (mastering->has_max_cll && mastering->max_cll > 0.0f) + return mastering->max_cll; + + if (mastering->has_luminance && mastering->max_lum > 0.0f) + return mastering->max_lum; + } + + return lum->mastering_max; +} + static gboolean -needs_tone_mapping (const ClutterLuminance *lum, - const ClutterLuminance *target_lum) +needs_tone_mapping (const ClutterLuminance *lum, + const ClutterColorMasteringMetadata *mastering, + const ClutterLuminance *target_lum, + const ClutterColorMasteringMetadata *target_mastering) { + float src_max, target_max; float ratio, target_ratio; - /* Common trivial case */ - if (lum->ref >= lum->mastering_max && - target_lum->ref <= target_lum->mastering_max) - return FALSE; + src_max = get_effective_mastering_max (lum, mastering); + target_max = get_effective_mastering_max (target_lum, target_mastering); - /* No tone mapping with HDR enabled for now */ - if (target_lum->mastering_max > target_lum->ref) + /* Common trivial case: source has no headroom above reference and the + * target's container can hold the full source volume. */ + if (lum->ref >= src_max && + target_lum->ref <= target_max && + src_max <= target_max) return FALSE; - ratio = (float) lum->mastering_max / lum->ref; - target_ratio = (float) target_lum->mastering_max / target_lum->ref; + ratio = src_max / lum->ref; + target_ratio = target_max / target_lum->ref; if (G_APPROX_VALUE (ratio, target_ratio, 0.1f)) return FALSE; @@ -629,10 +653,12 @@ needs_tone_mapping (const ClutterLuminance *lum, } static gboolean -needs_lum_mapping (const ClutterLuminance *lum, - const ClutterLuminance *target_lum) +needs_lum_mapping (const ClutterLuminance *lum, + const ClutterColorMasteringMetadata *mastering, + const ClutterLuminance *target_lum, + const ClutterColorMasteringMetadata *target_mastering) { - if (needs_tone_mapping (lum, target_lum)) + if (needs_tone_mapping (lum, mastering, target_lum, target_mastering)) return FALSE; return !G_APPROX_VALUE (target_lum->ref * lum->max, @@ -651,18 +677,25 @@ clutter_color_state_params_init_color_transform_key (ClutterColorState ClutterColorStateParams *target_color_state_params = CLUTTER_COLOR_STATE_PARAMS (target_color_state); const ClutterLuminance *lum, *target_lum; + const ClutterColorMasteringMetadata *mastering, *target_mastering; lum = clutter_color_state_params_get_luminance (color_state_params); target_lum = clutter_color_state_params_get_luminance (target_color_state_params); + mastering = + clutter_color_state_params_get_mastering_metadata (color_state_params); + target_mastering = + clutter_color_state_params_get_mastering_metadata (target_color_state_params); key->source_eotf_bits = get_eotf_key (color_state_params->eotf); key->target_eotf_bits = get_eotf_key (target_color_state_params->eotf); - key->luminance_bit = needs_lum_mapping (lum, target_lum) ? 1 : 0; + key->luminance_bit = + needs_lum_mapping (lum, mastering, target_lum, target_mastering) ? 1 : 0; key->color_trans_bit = colorimetry_equal (&color_state_params->colorimetry, &target_color_state_params->colorimetry) ? 0 : 1; - key->tone_mapping_bit = needs_tone_mapping (lum, target_lum) ? 1 : 0; + key->tone_mapping_bit = + needs_tone_mapping (lum, mastering, target_lum, target_mastering) ? 1 : 0; key->lut_3d = 0; key->opaque_bit = !!(flags & CLUTTER_COLOR_STATE_TRANSFORM_OPAQUE); } @@ -984,11 +1017,13 @@ static const ClutterColorOpSnippet luminance_mapping = { }; static void -get_luminance_mapping_snippet (const ClutterLuminance *lum, - const ClutterLuminance *target_lum, - const ClutterColorOpSnippet **luminance_mapping_snippet) +get_luminance_mapping_snippet (const ClutterLuminance *lum, + const ClutterColorMasteringMetadata *mastering, + const ClutterLuminance *target_lum, + const ClutterColorMasteringMetadata *target_mastering, + const ClutterColorOpSnippet **luminance_mapping_snippet) { - if (!needs_lum_mapping (lum, target_lum)) + if (!needs_lum_mapping (lum, mastering, target_lum, target_mastering)) return; *luminance_mapping_snippet = &luminance_mapping; @@ -1115,11 +1150,13 @@ get_color_space_mapping_snippet (ClutterColorStateParams *color_state_param } static void -get_tone_mapping_snippet (const ClutterLuminance *lum, - const ClutterLuminance *target_lum, - const ClutterColorOpSnippet **tone_mapping_snippet) +get_tone_mapping_snippet (const ClutterLuminance *lum, + const ClutterColorMasteringMetadata *mastering, + const ClutterLuminance *target_lum, + const ClutterColorMasteringMetadata *target_mastering, + const ClutterColorOpSnippet **tone_mapping_snippet) { - if (!needs_tone_mapping (lum, target_lum)) + if (!needs_tone_mapping (lum, mastering, target_lum, target_mastering)) return; *tone_mapping_snippet = &tone_mapping; @@ -1142,20 +1179,29 @@ clutter_color_state_params_append_transform_snippet (ClutterColorState *color_st ClutterColorStateParams *target_color_state_params = CLUTTER_COLOR_STATE_PARAMS (target_color_state); const ClutterLuminance *lum, *target_lum; + const ClutterColorMasteringMetadata *mastering, *target_mastering; lum = clutter_color_state_params_get_luminance (color_state_params); target_lum = clutter_color_state_params_get_luminance (target_color_state_params); + mastering = + clutter_color_state_params_get_mastering_metadata (color_state_params); + target_mastering = + clutter_color_state_params_get_mastering_metadata (target_color_state_params); get_eotf_snippets (color_state_params, target_color_state_params, &eotf_snippet, &inv_eotf_snippet); - get_luminance_mapping_snippet (lum, target_lum, &luminance_mapping_snippet); + get_luminance_mapping_snippet (lum, mastering, + target_lum, target_mastering, + &luminance_mapping_snippet); get_color_space_mapping_snippet (color_state_params, target_color_state_params, &color_space_mapping_snippet); - get_tone_mapping_snippet (lum, target_lum, &tone_mapping_snippet); + get_tone_mapping_snippet (lum, mastering, + target_lum, target_mastering, + &tone_mapping_snippet); /* * The following statements generate a shader snippet that transforms colors @@ -1654,12 +1700,17 @@ update_luminance_mapping_uniforms (ClutterColorStateParams *color_state_params, float lum_mapping; int uniform_location_luminance_mapping; const ClutterLuminance *lum, *target_lum; + const ClutterColorMasteringMetadata *mastering, *target_mastering; lum = clutter_color_state_params_get_luminance (color_state_params); target_lum = clutter_color_state_params_get_luminance (target_color_state_params); + mastering = + clutter_color_state_params_get_mastering_metadata (color_state_params); + target_mastering = + clutter_color_state_params_get_mastering_metadata (target_color_state_params); - if (!needs_lum_mapping (lum, target_lum)) + if (!needs_lum_mapping (lum, mastering, target_lum, target_mastering)) return; lum_mapping = get_lum_mapping (lum, target_lum); @@ -1728,13 +1779,15 @@ get_ictcp_mapping_matrices (graphene_matrix_t *out_to_ictcp, } static float -get_tonemapping_ref_lum (const ClutterLuminance *lum) +get_tonemapping_ref_lum (const ClutterLuminance *lum, + const ClutterColorMasteringMetadata *mastering) { + float mastering_max = get_effective_mastering_max (lum, mastering); float headroom; /* The tone mapper needs for dst lum at least a headroom of 1.5 */ - headroom = lum->mastering_max / lum->ref; - return headroom >= 1.5f ? lum->ref : lum->mastering_max / 1.5f; + headroom = mastering_max / lum->ref; + return headroom >= 1.5f ? lum->ref : mastering_max / 1.5f; } static void @@ -1753,16 +1806,26 @@ update_tone_mapping_uniforms (ClutterColorStateParams *color_state_params, int uniform_location_tonemapping_ref_lum; int uniform_location_linear_tonemapping; float tonemapping_ref_lum; + float src_mastering_max, dst_mastering_max; const ClutterLuminance *lum; const ClutterLuminance *target_lum; + const ClutterColorMasteringMetadata *mastering; + const ClutterColorMasteringMetadata *target_mastering; graphene_matrix_t to_LMS, from_LMS; lum = clutter_color_state_params_get_luminance (color_state_params); target_lum = clutter_color_state_params_get_luminance (target_color_state_params); + mastering = + clutter_color_state_params_get_mastering_metadata (color_state_params); + target_mastering = + clutter_color_state_params_get_mastering_metadata (target_color_state_params); - if (!needs_tone_mapping (lum, target_lum)) + if (!needs_tone_mapping (lum, mastering, target_lum, target_mastering)) return; + src_mastering_max = get_effective_mastering_max (lum, mastering); + dst_mastering_max = get_effective_mastering_max (target_lum, target_mastering); + clutter_color_state_params_get_to_LMS (target_color_state_params, &to_LMS); graphene_matrix_to_float (&to_LMS, matrix); @@ -1810,14 +1873,14 @@ update_tone_mapping_uniforms (ClutterColorStateParams *color_state_params, UNIFORM_NAME_SRC_MASTERING_MAX_LUM); cogl_pipeline_set_uniform_1f (pipeline, uniform_location_src_mastering_max_lum, - lum->mastering_max); + src_mastering_max); uniform_location_dst_mastering_max_lum = cogl_pipeline_get_uniform_location (pipeline, UNIFORM_NAME_DST_MASTERING_MAX_LUM); cogl_pipeline_set_uniform_1f (pipeline, uniform_location_dst_mastering_max_lum, - target_lum->mastering_max); + dst_mastering_max); uniform_location_src_ref_lum = cogl_pipeline_get_uniform_location (pipeline, @@ -1826,7 +1889,7 @@ update_tone_mapping_uniforms (ClutterColorStateParams *color_state_params, uniform_location_src_ref_lum, lum->ref); - tonemapping_ref_lum = get_tonemapping_ref_lum (target_lum); + tonemapping_ref_lum = get_tonemapping_ref_lum (target_lum, target_mastering); uniform_location_tonemapping_ref_lum = cogl_pipeline_get_uniform_location (pipeline, @@ -1871,13 +1934,16 @@ clutter_color_state_params_update_uniforms (ClutterColorState *color_state, } static void -clutter_luminance_apply_tone_mapping (const ClutterLuminance *lum, - const ClutterLuminance *target_lum, - float *data, - int n_samples) +clutter_luminance_apply_tone_mapping (const ClutterLuminance *lum, + const ClutterColorMasteringMetadata *mastering, + const ClutterLuminance *target_lum, + const ClutterColorMasteringMetadata *target_mastering, + float *data, + int n_samples) { float result[4]; float tonemapping_ref_lum, luminance; + float src_mastering_max, dst_mastering_max; graphene_matrix_t to_LMS, from_LMS; graphene_matrix_t to_D65, from_D65; graphene_matrix_t to_ictcp, from_ictcp; @@ -1899,7 +1965,9 @@ clutter_luminance_apply_tone_mapping (const ClutterLuminance *lum, get_ictcp_mapping_matrices (&to_ictcp, &from_ictcp); - tonemapping_ref_lum = get_tonemapping_ref_lum (target_lum); + tonemapping_ref_lum = get_tonemapping_ref_lum (target_lum, target_mastering); + src_mastering_max = get_effective_mastering_max (lum, mastering); + dst_mastering_max = get_effective_mastering_max (target_lum, target_mastering); for (i = 0; i < n_samples; i++) { @@ -1931,10 +1999,10 @@ clutter_luminance_apply_tone_mapping (const ClutterLuminance *lum, } else { - float ratio = (luminance - lum->ref) / (lum->mastering_max - lum->ref); + float ratio = (luminance - lum->ref) / (src_mastering_max - lum->ref); luminance = tonemapping_ref_lum + - (target_lum->mastering_max - tonemapping_ref_lum) * + (dst_mastering_max - tonemapping_ref_lum) * 5.0f * ratio / (4.0f * ratio + 1.0f); } result[0] = clutter_eotf_apply_pq_inv (luminance / target_lum->max); @@ -1963,15 +2031,17 @@ clutter_luminance_apply_tone_mapping (const ClutterLuminance *lum, } static void -clutter_luminance_apply_luminance_mapping (const ClutterLuminance *lum, - const ClutterLuminance *target_lum, - float *data, - int n_samples) +clutter_luminance_apply_luminance_mapping (const ClutterLuminance *lum, + const ClutterColorMasteringMetadata *mastering, + const ClutterLuminance *target_lum, + const ClutterColorMasteringMetadata *target_mastering, + float *data, + int n_samples) { float lum_mapping; int i; - if (!needs_lum_mapping (lum, target_lum)) + if (!needs_lum_mapping (lum, mastering, target_lum, target_mastering)) return; lum_mapping = get_lum_mapping (lum, target_lum); @@ -2080,12 +2150,16 @@ clutter_color_state_params_do_tone_mapping (ClutterColorState *color_state, { const ClutterLuminance *src_lum; const ClutterLuminance *dst_lum; + const ClutterColorMasteringMetadata *src_mastering = NULL; + const ClutterColorMasteringMetadata *dst_mastering = NULL; ClutterColorStateParams *color_state_params; if (CLUTTER_IS_COLOR_STATE_PARAMS (color_state)) { color_state_params = CLUTTER_COLOR_STATE_PARAMS (color_state); src_lum = clutter_color_state_params_get_luminance (color_state_params); + src_mastering = + clutter_color_state_params_get_mastering_metadata (color_state_params); } else { @@ -2096,23 +2170,25 @@ clutter_color_state_params_do_tone_mapping (ClutterColorState *color_state, { color_state_params = CLUTTER_COLOR_STATE_PARAMS (other_color_state); dst_lum = clutter_color_state_params_get_luminance (color_state_params); + dst_mastering = + clutter_color_state_params_get_mastering_metadata (color_state_params); } else { dst_lum = &sdr_default_luminance; } - if (needs_tone_mapping (src_lum, dst_lum)) + if (needs_tone_mapping (src_lum, src_mastering, dst_lum, dst_mastering)) { - clutter_luminance_apply_tone_mapping (src_lum, - dst_lum, + clutter_luminance_apply_tone_mapping (src_lum, src_mastering, + dst_lum, dst_mastering, data, n_samples); } - else if (needs_lum_mapping (src_lum, dst_lum)) + else if (needs_lum_mapping (src_lum, src_mastering, dst_lum, dst_mastering)) { - clutter_luminance_apply_luminance_mapping (src_lum, - dst_lum, + clutter_luminance_apply_luminance_mapping (src_lum, src_mastering, + dst_lum, dst_mastering, data, n_samples); } @@ -2153,6 +2229,7 @@ clutter_color_state_params_needs_mapping (ClutterColorState *color_state, ClutterColorStateParams *target_color_state_params = CLUTTER_COLOR_STATE_PARAMS (target_color_state); const ClutterLuminance *lum, *target_lum; + const ClutterColorMasteringMetadata *mastering, *target_mastering; if (!colorimetry_equal (&color_state_params->colorimetry, &target_color_state_params->colorimetry) || @@ -2162,9 +2239,13 @@ clutter_color_state_params_needs_mapping (ClutterColorState *color_state, lum = clutter_color_state_params_get_luminance (color_state_params); target_lum = clutter_color_state_params_get_luminance (target_color_state_params); + mastering = + clutter_color_state_params_get_mastering_metadata (color_state_params); + target_mastering = + clutter_color_state_params_get_mastering_metadata (target_color_state_params); - return needs_tone_mapping (lum, target_lum) || - needs_lum_mapping (lum, target_lum); + return needs_tone_mapping (lum, mastering, target_lum, target_mastering) || + needs_lum_mapping (lum, mastering, target_lum, target_mastering); } static char * diff --git a/src/backends/meta-color-device.c b/src/backends/meta-color-device.c index f0572caa3b3..cb6332f97ca 100644 --- a/src/backends/meta-color-device.c +++ b/src/backends/meta-color-device.c @@ -702,11 +702,18 @@ fill_hdr_metadata_from_edid (const MetaEdidInfo *edid_info, if (max_lum > 0.0f) { - /* For PQ the encoded signal range is fixed; the display's actual - * capability is carried as the mastering luminance. */ + /* For PQ the encoded signal range is fixed (0-10000 nits per spec); the + * display's actual capability is carried as the mastering luminance so + * the tone mapper compresses content into the panel's range. Seed the + * container with the PQ defaults so the inverse-EOTF chain encodes + * values relative to 10000 cd/m² as the spec requires; override only + * the mastering peak with the EDID value. */ + ClutterEOTF pq_eotf = { + .type = CLUTTER_EOTF_TYPE_NAMED, + .tf_name = CLUTTER_TRANSFER_FUNCTION_PQ, + }; + *luminance = *clutter_eotf_get_default_luminance (pq_eotf); luminance->type = CLUTTER_LUMINANCE_TYPE_EXPLICIT; - luminance->min = min_lum; - luminance->max = max_lum; luminance->ref = HDR_REFERENCE_LUMINANCE; luminance->mastering_max = max_lum; -- GitLab From d51dfbe58f04b4665b2b608d9f2ab53cf095b7fd Mon Sep 17 00:00:00 2001 From: Christopher Snowhill Date: Sat, 30 May 2026 06:42:06 -0700 Subject: [PATCH 3/5] color-management: Don't tone-map sources without mastering metadata MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a client attaches an HDR10 image description (BT.2020 + PQ) without also calling set_max_cll / set_mastering_luminance, the source mastering struct stays empty and get_effective_mastering_max fell back to lum->mastering_max. For the PQ default container that is 10000 nits, so needs_tone_mapping always compressed the source against a fixed 0–10000 range — collapsing in-game calibration into a single output level no matter what brightness the game actually produced. Fall back to the reference luminance instead. With no explicit hint about how bright the content goes, ref is the conservative assumption: the trivial-case early-out in needs_tone_mapping then fires, no curve runs, and the display handles values beyond its peak. This mirrors KWin's value_or(referenceLuminance) fallback in opengl/glshader.cpp:setColorspaceUniforms() and its clip-only path in opengl/colormanagement.glsl::doTonemapping(). Sources that do supply mastering metadata (max_cll, max_lum) still drive the modified-Reinhard curve as before. Assisted-By: Claude Opus 4.7 --- clutter/clutter/clutter-color-state-params.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/clutter/clutter/clutter-color-state-params.c b/clutter/clutter/clutter-color-state-params.c index e63e5e43d7f..724f66a7772 100644 --- a/clutter/clutter/clutter-color-state-params.c +++ b/clutter/clutter/clutter-color-state-params.c @@ -606,8 +606,12 @@ get_eotf_key (ClutterEOTF eotf) /* Best estimate of the peak luminance to use as the mastering ceiling for * tone mapping. Prefers explicit mastering metadata when the client (or EDID - * parser) provided it, falling back to the container's nominal mastering max - * (e.g. 10000 nits for PQ, 80 nits for sRGB). */ + * parser) provided it. With no explicit hint, fall back to the reference + * luminance: assuming the content fills the entire container (e.g. 10000 + * nits for PQ) would otherwise compress every signal against that ceiling + * regardless of the actual brightness the source produces, crushing in-game + * calibration into a single output level. Matches the value_or(reference) + * fallback in KWin's tone mapper. */ static float get_effective_mastering_max (const ClutterLuminance *lum, const ClutterColorMasteringMetadata *mastering) @@ -621,7 +625,7 @@ get_effective_mastering_max (const ClutterLuminance *lum, return mastering->max_lum; } - return lum->mastering_max; + return lum->ref; } static gboolean -- GitLab From 93712cf7ec03c0bd59d039da08ab4a232e85aa7b Mon Sep 17 00:00:00 2001 From: Christopher Snowhill Date: Sat, 30 May 2026 06:43:41 -0700 Subject: [PATCH 4/5] wayland/color-management: Allow get_information on client image descriptions The protocol requires that get_information work on every successful image description: parametric ones must send primaries, tf, luminances, target_primaries and target_luminance; ICC ones must send icc_file. Mutter only flagged the output's color state and the surface_feedback preferred description with ALLOW_INFO, so calling get_information on a client-built image description (ICC creator, parametric creator, or create_windows_scrgb) posted the NO_INFORMATION error instead of delivering the events. KWin handles this in wayland/colormanagement_v1.cpp::wp_image_description_v1_get_information where only failed descriptions refuse the request. Set ALLOW_INFO on those three creation paths so spec-conformant clients that introspect their own image descriptions get the events they expect. Assisted-By: Claude Opus 4.7 --- src/wayland/meta-wayland-color-management.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/wayland/meta-wayland-color-management.c b/src/wayland/meta-wayland-color-management.c index 8d596c00dde..89dbbf3fc96 100644 --- a/src/wayland/meta-wayland-color-management.c +++ b/src/wayland/meta-wayland-color-management.c @@ -1158,7 +1158,8 @@ on_icc_create_bytes_read (GObject *source_object, meta_wayland_image_description_new_color_state (color_manager, image_desc_resource, color_state, - META_WAYLAND_IMAGE_DESCRIPTION_FLAGS_DEFAULT); + META_WAYLAND_IMAGE_DESCRIPTION_FLAGS_DEFAULT | + META_WAYLAND_IMAGE_DESCRIPTION_FLAGS_ALLOW_INFO); } else { @@ -1355,7 +1356,8 @@ creator_params_create (struct wl_client *client, meta_wayland_image_description_new_color_state (color_manager, image_desc_resource, color_state, - META_WAYLAND_IMAGE_DESCRIPTION_FLAGS_DEFAULT); + META_WAYLAND_IMAGE_DESCRIPTION_FLAGS_DEFAULT | + META_WAYLAND_IMAGE_DESCRIPTION_FLAGS_ALLOW_INFO); wl_resource_set_implementation (image_desc_resource, &meta_wayland_image_description_interface, @@ -1985,7 +1987,8 @@ color_manager_create_windows_scrgb (struct wl_client *client, meta_wayland_image_description_new_color_state (color_manager, image_desc_resource, color_state, - META_WAYLAND_IMAGE_DESCRIPTION_FLAGS_DEFAULT); + META_WAYLAND_IMAGE_DESCRIPTION_FLAGS_DEFAULT | + META_WAYLAND_IMAGE_DESCRIPTION_FLAGS_ALLOW_INFO); wl_resource_set_implementation (image_desc_resource, &meta_wayland_image_description_interface, -- GitLab From 50d17629445c4502ff76bbd77954a750dcf3a85d Mon Sep 17 00:00:00 2001 From: Christopher Snowhill Date: Wed, 3 Jun 2026 01:30:53 -0700 Subject: [PATCH 5/5] wayland/color-management: Add windows_bt2100 image description support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit wp_color_manager_v1 v3 adds a create_windows_bt2100 request matching the Windows-BT.2100 stimulus encoding that HDR10 PQ games on Windows expect: BT.2020 primaries, ST 2084 PQ transfer, reference white 203 cd/m² per BT.2408, and an unspecified target color volume that Windows passes to the panel without any compositor-side adjustments. Without it, Proton's winewayland.drv logs "Compositor doesn't supports windows_bt2100, HDR10 ST2084 may look broken" and falls back to a parametric description whose semantics don't match the Windows passthrough behaviour. Vendor the v3 protocol XML locally (the system wayland-protocols still ships v2) and bump META_WP_COLOR_MANAGEMENT_VERSION accordingly. The new handler mirrors create_windows_scrgb: build a ClutterColorState with BT.2020 + PQ + (min 0.005, max 10000, ref 203) and mastering metadata containing only the BT.2020 primaries hint. Leaving has_luminance, has_max_cll and has_max_fall unset is deliberate: it lets get_effective_mastering_max fall back to lum->ref so the trivial case in needs_tone_mapping (src_max == lum->ref ≤ target_max) fires and the ICtCp tone curve is bypassed. The linear lum_mapping step still runs and naturally hard-clips signal above the panel peak, matching the protocol's note that "Windows-BT.2100 is generally displayed by Windows 10 without any adjustments to the signal". KWin's wayland/colormanagement_v1.cpp::wp_color_manager_v1_create_windows_bt2100 is the parallel reference implementation. The feature is advertised only to clients bound at v3; the test client now binds at v3 to exercise it, which required swapping the deprecated transfer_function_srgb (rejected at v2+) for transfer_function_compound_power_2_4 in the existing sync-point-2 setup and adding a ready2 listener for the v2+ image description identity event. A sync-point-7 assertion verifies the resulting ClutterColorState and that mastering carries only primaries. Assisted-By: Claude Opus 4.7 --- src/meson.build | 2 +- src/tests/wayland-color-management-test.c | 23 +- .../wayland-test-clients/color-management.c | 52 +- .../wayland-test-client-utils.c | 2 +- src/wayland/meta-wayland-color-management.c | 73 + src/wayland/meta-wayland-versions.h | 2 +- src/wayland/protocol/color-management-v1.xml | 1756 +++++++++++++++++ 7 files changed, 1904 insertions(+), 6 deletions(-) create mode 100644 src/wayland/protocol/color-management-v1.xml diff --git a/src/meson.build b/src/meson.build index df718a2aefd..2d4af1ab757 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1013,7 +1013,7 @@ wayland_protocol_sources = [] # * 'private' are shipped in this repo # - protocol version (optional for 'stable', 'staging' and 'unstable') wayland_protocols = [ - ['color-management', 'staging', 1, ], + ['color-management-v1', 'private', ], ['color-representation', 'staging', 1, ], ['commit-timing', 'staging', 1, ], ['cursor-shape', 'staging', 1, ], diff --git a/src/tests/wayland-color-management-test.c b/src/tests/wayland-color-management-test.c index 39b8664ae9e..94f7f3e98b5 100644 --- a/src/tests/wayland-color-management-test.c +++ b/src/tests/wayland-color-management-test.c @@ -124,7 +124,7 @@ color_management (void) g_assert_cmpuint (colorimetry->colorspace, ==, CLUTTER_COLORSPACE_SRGB); eotf = clutter_color_state_params_get_eotf (color_state_params); g_assert_cmpuint (eotf->type, ==, CLUTTER_EOTF_TYPE_NAMED); - g_assert_cmpuint (eotf->tf_name, ==, CLUTTER_TRANSFER_FUNCTION_GAMMA22); + g_assert_cmpuint (eotf->tf_name, ==, CLUTTER_TRANSFER_FUNCTION_SRGB_PIECEWISE); lum = clutter_color_state_params_get_luminance (color_state_params); g_assert_cmpuint (lum->type, ==, CLUTTER_LUMINANCE_TYPE_EXPLICIT); g_assert_cmpfloat (lum->mastering_max, ==, lum->max); @@ -207,6 +207,27 @@ color_management (void) g_assert_true (mastering->has_primaries); emit_sync_event (6); + wait_for_sync_point (7); + color_state = get_window_color_state (test_window); + color_state_params = CLUTTER_COLOR_STATE_PARAMS (color_state); + colorimetry = clutter_color_state_params_get_colorimetry (color_state_params); + g_assert_cmpuint (colorimetry->type, ==, CLUTTER_COLORIMETRY_TYPE_COLORSPACE); + g_assert_cmpuint (colorimetry->colorspace, ==, CLUTTER_COLORSPACE_BT2020); + eotf = clutter_color_state_params_get_eotf (color_state_params); + g_assert_cmpuint (eotf->type, ==, CLUTTER_EOTF_TYPE_NAMED); + g_assert_cmpuint (eotf->tf_name, ==, CLUTTER_TRANSFER_FUNCTION_PQ); + lum = clutter_color_state_params_get_luminance (color_state_params); + g_assert_cmpuint (lum->type, ==, CLUTTER_LUMINANCE_TYPE_EXPLICIT); + g_assert_cmpfloat_with_epsilon (lum->min, 0.005f, TEST_COLOR_EPSILON); + g_assert_cmpfloat_with_epsilon (lum->max, lum->min + 10000.0f, TEST_COLOR_EPSILON); + g_assert_cmpfloat_with_epsilon (lum->ref, 203.0f, TEST_COLOR_EPSILON); + mastering = clutter_color_state_params_get_mastering_metadata (color_state_params); + g_assert_true (mastering->has_primaries); + g_assert_false (mastering->has_luminance); + g_assert_false (mastering->has_max_cll); + g_assert_false (mastering->has_max_fall); + emit_sync_event (7); + meta_wayland_test_client_finish (wayland_test_client); } diff --git a/src/tests/wayland-test-clients/color-management.c b/src/tests/wayland-test-clients/color-management.c index 8d3282fbb53..12b2ec6fba5 100644 --- a/src/tests/wayland-test-clients/color-management.c +++ b/src/tests/wayland-test-clients/color-management.c @@ -30,7 +30,7 @@ typedef struct _ImageDescriptionContext { - uint32_t image_description_id; + uint64_t image_description_id; gboolean creation_failed; } ImageDescriptionContext; @@ -127,9 +127,22 @@ handle_image_description_ready (void *data, image_description_context->image_description_id = identity; } +static void +handle_image_description_ready2 (void *data, + struct wp_image_description_v1 *image_description_v4, + uint32_t identity_high, + uint32_t identity_low) +{ + ImageDescriptionContext *image_description_context = data; + + image_description_context->image_description_id = + ((uint64_t) identity_high << 32) | identity_low; +} + static const struct wp_image_description_v1_listener image_description_listener = { handle_image_description_failed, handle_image_description_ready, + handle_image_description_ready2, }; static void @@ -328,6 +341,28 @@ create_image_description_windows_scrgb (WaylandDisplay *display g_assert_cmpint (image_description_context.image_description_id, >, 0); } +static void +create_image_description_windows_bt2100 (WaylandDisplay *display, + struct wp_image_description_v1 **image_description) +{ + ImageDescriptionContext image_description_context; + + image_description_context.image_description_id = 0; + image_description_context.creation_failed = FALSE; + + *image_description = + wp_color_manager_v1_create_windows_bt2100 (display->color_management_mgr); + wp_image_description_v1_add_listener ( + *image_description, + &image_description_listener, + &image_description_context); + + wait_for_image_description_ready (&image_description_context, display); + + g_assert_false (image_description_context.creation_failed); + g_assert_cmpint (image_description_context.image_description_id, >, 0); +} + int main (int argc, char **argv) @@ -382,7 +417,7 @@ main (int argc, &image_description, WP_COLOR_MANAGER_V1_PRIMARIES_SRGB, NULL, - WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_SRGB, + WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_COMPOUND_POWER_2_4, -1.0f, 0.2f, 80.0f, @@ -465,6 +500,19 @@ main (int argc, test_driver_sync_point (display->test_driver, 6, NULL); wait_for_sync_event (display, 6); + create_image_description_windows_bt2100 (display, &image_description); + wp_color_management_surface_v1_set_image_description ( + color_surface, + image_description, + WP_COLOR_MANAGER_V1_RENDER_INTENT_PERCEPTUAL); + + wl_surface_commit (surface); + + wp_image_description_v1_destroy (image_description); + + test_driver_sync_point (display->test_driver, 7, NULL); + wait_for_sync_event (display, 7); + wp_color_management_surface_v1_destroy (color_surface); return EXIT_SUCCESS; diff --git a/src/tests/wayland-test-clients/wayland-test-client-utils.c b/src/tests/wayland-test-clients/wayland-test-client-utils.c index 4bfb5bc9cf3..b0eee7fc87c 100644 --- a/src/tests/wayland-test-clients/wayland-test-client-utils.c +++ b/src/tests/wayland-test-clients/wayland-test-client-utils.c @@ -860,7 +860,7 @@ handle_registry_global (void *user_data, { display->color_management_mgr = wl_registry_bind (registry, id, - &wp_color_manager_v1_interface, 1); + &wp_color_manager_v1_interface, 3); } else if (strcmp (interface, wp_cursor_shape_manager_v1_interface.name) == 0) { diff --git a/src/wayland/meta-wayland-color-management.c b/src/wayland/meta-wayland-color-management.c index 89dbbf3fc96..d6c0caa25ee 100644 --- a/src/wayland/meta-wayland-color-management.c +++ b/src/wayland/meta-wayland-color-management.c @@ -1996,6 +1996,73 @@ color_manager_create_windows_scrgb (struct wl_client *client, image_description_destructor); } +static void +color_manager_create_windows_bt2100 (struct wl_client *client, + struct wl_resource *resource, + uint32_t id) +{ + MetaWaylandColorManager *color_manager = wl_resource_get_user_data (resource); + ClutterContext *clutter_context = get_clutter_context (color_manager); + struct wl_resource *image_desc_resource; + g_autoptr (ClutterColorState) color_state = NULL; + MetaWaylandImageDescription *image_desc; + ClutterColorimetry colorimetry; + ClutterEOTF eotf; + ClutterLuminance luminance; + ClutterColorMasteringMetadata mastering; + + /* Windows-BT.2100: BT.2020 primaries with the ST 2084 (PQ) transfer + * characteristic. Reference white is assumed 203 cd/m² per BT.2408. The + * target color volume is unspecified by the protocol; mastering metadata + * is intentionally left unset so tone mapping is bypassed, matching how + * Windows passes the PQ signal to the display without further + * adjustments. */ + colorimetry = (ClutterColorimetry) { + .type = CLUTTER_COLORIMETRY_TYPE_COLORSPACE, + .colorspace = CLUTTER_COLORSPACE_BT2020, + }; + eotf = (ClutterEOTF) { + .type = CLUTTER_EOTF_TYPE_NAMED, + .tf_name = CLUTTER_TRANSFER_FUNCTION_PQ, + }; + luminance = (ClutterLuminance) { + .type = CLUTTER_LUMINANCE_TYPE_EXPLICIT, + .min = 0.005f, + .max = 10000.0f, + .ref = 203.0f, + .mastering_max = 10000.0f, + }; + mastering = (ClutterColorMasteringMetadata) { + .has_primaries = TRUE, + .primaries = *clutter_colorspace_to_primaries (CLUTTER_COLORSPACE_BT2020), + }; + + color_state = + clutter_color_state_params_new_with_mastering (clutter_context, + colorimetry, + eotf, + luminance, + &mastering); + + image_desc_resource = + wl_resource_create (client, + &wp_image_description_v1_interface, + wl_resource_get_version (resource), + id); + + image_desc = + meta_wayland_image_description_new_color_state (color_manager, + image_desc_resource, + color_state, + META_WAYLAND_IMAGE_DESCRIPTION_FLAGS_DEFAULT | + META_WAYLAND_IMAGE_DESCRIPTION_FLAGS_ALLOW_INFO); + + wl_resource_set_implementation (image_desc_resource, + &meta_wayland_image_description_interface, + image_desc, + image_description_destructor); +} + static void color_manager_get_image_description (struct wl_client *client, struct wl_resource *resource, @@ -2028,6 +2095,11 @@ color_manager_send_supported_events (struct wl_resource *resource) WP_COLOR_MANAGER_V1_FEATURE_EXTENDED_TARGET_VOLUME); wp_color_manager_v1_send_supported_feature (resource, WP_COLOR_MANAGER_V1_FEATURE_WINDOWS_SCRGB); + if (wl_resource_get_version (resource) >= 3) + { + wp_color_manager_v1_send_supported_feature (resource, + WP_COLOR_MANAGER_V1_FEATURE_WINDOWS_BT2100); + } wp_color_manager_v1_send_supported_tf_named (resource, WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA22); wp_color_manager_v1_send_supported_tf_named (resource, @@ -2076,6 +2148,7 @@ static const struct wp_color_manager_v1_interface color_manager_create_parametric_creator, color_manager_create_windows_scrgb, color_manager_get_image_description, + color_manager_create_windows_bt2100, }; static void diff --git a/src/wayland/meta-wayland-versions.h b/src/wayland/meta-wayland-versions.h index 9da5e1eeeb0..ed8a7326cfa 100644 --- a/src/wayland/meta-wayland-versions.h +++ b/src/wayland/meta-wayland-versions.h @@ -57,7 +57,7 @@ #define META_WP_SINGLE_PIXEL_BUFFER_V1_VERSION 1 #define META_MUTTER_X11_INTEROP_VERSION 1 #define META_WP_FRACTIONAL_SCALE_VERSION 1 -#define META_WP_COLOR_MANAGEMENT_VERSION 2 +#define META_WP_COLOR_MANAGEMENT_VERSION 3 #define META_XDG_DIALOG_VERSION 1 #define META_WP_DRM_LEASE_DEVICE_V1_VERSION 1 #define META_XDG_SESSION_MANAGER_V1_VERSION 1 diff --git a/src/wayland/protocol/color-management-v1.xml b/src/wayland/protocol/color-management-v1.xml new file mode 100644 index 00000000000..b47e1080e26 --- /dev/null +++ b/src/wayland/protocol/color-management-v1.xml @@ -0,0 +1,1756 @@ + + + + Copyright 2019 Sebastian Wick + Copyright 2019 Erwin Burema + Copyright 2020 AMD + Copyright 2020-2024 Collabora, Ltd. + Copyright 2024 Xaver Hugl + Copyright 2022-2025 Red Hat, Inc. + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + The aim of the color management extension is to allow clients to know + the color properties of outputs, and to tell the compositor about the color + properties of their content on surfaces. All surface contents must be + readily intended for some display, but not necessarily for the display at + hand. Doing this enables a compositor to perform automatic color management + of content for different outputs according to how content is intended to + look like. + + For an introduction, see the section "Color management" in the Wayland + documentation at https://wayland.freedesktop.org/docs/html/ . + + The color properties are represented as an image description object which + is immutable after it has been created. A wl_output always has an + associated image description that clients can observe. A wl_surface + always has an associated preferred image description as a hint chosen by + the compositor that clients can also observe. Clients can set an image + description on a wl_surface to denote the color characteristics of the + surface contents. + + An image description essentially defines a display and (indirectly) its + viewing environment. An image description includes SDR and HDR colorimetry + and encoding, HDR metadata, and some parameters related to the viewing + environment. An image description does not include the properties set + through color-representation extension. It is expected that the + color-representation extension is used in conjunction with the + color-management extension when necessary, particularly with the YUV family + of pixel formats. + + The normative appendix for this protocol is in the appendix.md file beside + this XML file. + + The color-and-hdr repository + (https://gitlab.freedesktop.org/pq/color-and-hdr) contains + background information on the protocol design and legacy color management. + It also contains a glossary, learning resources for digital color, tools, + samples and more. + + The terminology used in this protocol is based on common color science and + color encoding terminology where possible. The glossary in the color-and-hdr + repository shall be the authority on the definition of terms in this + protocol. + + Warning! The protocol described in this file is currently in the testing + phase. Backward compatible changes may be added together with the + corresponding interface version bump. Backward incompatible changes can + only be done by creating a new major version of the extension. + + + + + A singleton global interface used for getting color management extensions + for wl_surface and wl_output objects, and for creating client defined + image description objects. The extension interfaces allow + getting the image description of outputs and setting the image + description of surfaces. + + Compositors should never remove this global. + + + + + Destroy the wp_color_manager_v1 object. This does not affect any other + objects in any way. + + + + + + + + + + + See the ICC.1:2022 specification from the International Color Consortium + for more details about rendering intents. + + The principles of ICC defined rendering intents apply with all types of + image descriptions, not only those with ICC file profiles. + + Compositors must support the perceptual rendering intent. Other + rendering intents are optional. + + + + + + + + + + This rendering intent is a modified absolute rendering intent that + assumes the viewer is not adapted to the display white point, so no + chromatic adaptation between surface and display is done. + This can be useful for color proofing applications. + + + + + + + + + + + + + + + The compositor supports set_mastering_display_primaries request with a + target color volume fully contained inside the primary color volume. + + + + + The compositor additionally supports target color volumes that + extend outside of the primary color volume. + + This can only be advertised if feature set_mastering_display_primaries + is supported as well. + + + + + + + + + Named color primaries used to encode well-known sets of primaries. + + A value of 0 is invalid and will never be present in the list of enums. + + + + + Color primaries as defined by + - Rec. ITU-R BT.709-6 + - Rec. ITU-R BT.1361-0 conventional colour gamut system and extended + colour gamut system (historical) + - IEC 61966-2-1 sRGB or sYCC + - IEC 61966-2-4 + - Society of Motion Picture and Television Engineers (SMPTE) RP 177 + (1993) Annex B + + + + + Color primaries as defined by + - Rec. ITU-R BT.470-6 System M (historical) + - United States National Television System Committee 1953 + Recommendation for transmission standards for color television + - United States Federal Communications Commission (2003) Title 47 Code + of Federal Regulations 73.682 (a)(20) + + + + + Color primaries as defined by + - Rec. ITU-R BT.470-6 System B, G (historical) + - Rec. ITU-R BT.601-7 625 + - Rec. ITU-R BT.1358-0 625 (historical) + - Rec. ITU-R BT.1700-0 625 PAL and 625 SECAM + + + + + Color primaries as defined by + - Rec. ITU-R BT.601-7 525 + - Rec. ITU-R BT.1358-1 525 or 625 (historical) + - Rec. ITU-R BT.1700-0 NTSC + - SMPTE 170M (2004) + - SMPTE 240M (1999) (historical) + + + + + Color primaries as defined by Recommendation ITU-T H.273 + "Coding-independent code points for video signal type identification" + for "generic film". + + + + + Color primaries as defined by + - Rec. ITU-R BT.2020-2 + - Rec. ITU-R BT.2100-0 + + + + + Color primaries as defined as the maximum of the CIE 1931 XYZ color + space by + - SMPTE ST 428-1 + - (CIE 1931 XYZ as in ISO 11664-1) + + + + + Color primaries as defined by Digital Cinema System and published in + SMPTE RP 431-2 (2011). + + + + + Color primaries as defined by Digital Cinema System and published in + SMPTE EG 432-1 (2010). + + + + + Color primaries as defined by Adobe as "Adobe RGB" and later published + by ISO 12640-4 (2011). + + + + + + + Named transfer functions used to represent well-known transfer + characteristics of displays. + + A value of 0 is invalid and will never be present in the list of enums. + + See appendix.md for the formulae. + + + + + Rec. ITU-R BT.1886 is the display transfer characteristic assumed by + - Rec. ITU-R BT.601-7 525 and 625 + - Rec. ITU-R BT.709-6 + - Rec. ITU-R BT.2020-2 + + This TF implies these default luminances from Rec. ITU-R BT.2035: + - primary color volume minimum: 0.01 cd/m² + - primary color volume maximum: 100 cd/m² + - reference white: 100 cd/m² + + + + + Transfer characteristics as defined by + - Rec. ITU-R BT.470-6 System M (historical) + - United States National Television System Committee 1953 + Recommendation for transmission standards for color television + - United States Federal Communications Commission (2003) Title 47 Code + of Federal Regulations 73.682 (a) (20) + - Rec. ITU-R BT.1700-0 625 PAL and 625 SECAM + - IEC 61966-2-1 (reference display) + + + + + Transfer characteristics as defined by + - Rec. ITU-R BT.470-6 System B, G (historical) + + + + + Transfer characteristics as defined by + - SMPTE ST 240 (1999) + + + + + Linear transfer function defined over all real numbers. + Normalised electrical values are equal the normalised optical values. + + + + + Logarithmic transfer characteristic (100:1 range). + + + + + Logarithmic transfer characteristic (100 * Sqrt(10) : 1 range). + + + + + Transfer characteristics as defined by + - IEC 61966-2-4 + + + + + Transfer characteristics as defined by + - IEC 61966-2-1 sRGB + + As a rule of thumb, use gamma22 for video, motion picture and + computer graphics, or compound_power_2_4 for ICC calibrated print + workflows. + + + + + Transfer characteristics as defined by + - IEC 61966-2-1 sYCC + + + + + Transfer characteristics as defined by + - SMPTE ST 2084 (2014) for 10-, 12-, 14- and 16-bit systems + - Rec. ITU-R BT.2100-2 perceptual quantization (PQ) system + + This TF implies these default luminances + - primary color volume minimum: 0.005 cd/m² + - primary color volume maximum: 10000 cd/m² + - reference white: 203 cd/m² + + The difference between the primary color volume minimum and maximum + must be approximately 10000 cd/m² as that is the swing of the EOTF + defined by ST 2084 and BT.2100. The default value for the + reference white is a protocol addition: it is suggested by + Report ITU-R BT.2408-7 and is not part of ST 2084 or BT.2100. + + + + + Transfer characteristics as defined by + - SMPTE ST 428-1 (2019) + + + + + Transfer characteristics as defined by + - ARIB STD-B67 (2015) + - Rec. ITU-R BT.2100-2 hybrid log-gamma (HLG) system + + This TF implies these default luminances + - primary color volume minimum: 0.005 cd/m² + - primary color volume maximum: 1000 cd/m² + - reference white: 203 cd/m² + + HLG is a relative display-referred signal with a specified + non-linear mapping to the display peak luminance (the HLG OOTF). + All absolute luminance values used here for HLG assume a 1000 cd/m² + peak display. + + The default value for the reference white is a protocol addition: + it is suggested by Report ITU-R BT.2408-7 and is not part of + ARIB STD-B67 or BT.2100. + + + + + Encoding characteristics as defined by IEC 61966-2-1, for displays + that invert the encoding function. + + + + + + + This creates a new wp_color_management_output_v1 object for the + given wl_output. + + See the wp_color_management_output_v1 interface for more details. + + + + + + + + + If a wp_color_management_surface_v1 object already exists for the given + wl_surface, the protocol error surface_exists is raised. + + This creates a new color wp_color_management_surface_v1 object for the + given wl_surface. + + See the wp_color_management_surface_v1 interface for more details. + + + + + + + + + This creates a new color wp_color_management_surface_feedback_v1 object + for the given wl_surface. + + See the wp_color_management_surface_feedback_v1 interface for more + details. + + + + + + + + + Makes a new ICC-based image description creator object with all + properties initially unset. The client can then use the object's + interface to define all the required properties for an image description + and finally create a wp_image_description_v1 object. + + This request can be used when the compositor advertises + wp_color_manager_v1.feature.icc_v2_v4. + Otherwise this request raises the protocol error unsupported_feature. + + + + + + + + Makes a new parametric image description creator object with all + properties initially unset. The client can then use the object's + interface to define all the required properties for an image description + and finally create a wp_image_description_v1 object. + + This request can be used when the compositor advertises + wp_color_manager_v1.feature.parametric. + Otherwise this request raises the protocol error unsupported_feature. + + + + + + + + This creates a pre-defined image description for the so-called + Windows-scRGB stimulus encoding. This comes from the Windows 10 handling + of its own definition of an scRGB color space for an HDR screen + driven in BT.2100/PQ signalling mode. + + Windows-scRGB uses sRGB (BT.709) color primaries and white point. + The transfer characteristic is extended linear. + + The nominal color channel value range is extended, meaning it includes + negative and greater than 1.0 values. Negative values are used to + escape the sRGB color gamut boundaries. To make use of the extended + range, the client needs to use a pixel format that can represent those + values, e.g. floating-point 16 bits per channel. + + Nominal color value R=G=B=0.0 corresponds to BT.2100/PQ system + 0 cd/m², and R=G=B=1.0 corresponds to BT.2100/PQ system 80 cd/m². + The maximum is R=G=B=125.0 corresponding to 10k cd/m². + + Windows-scRGB is displayed by Windows 10 by converting it to + BT.2100/PQ, maintaining the CIE 1931 chromaticity and mapping the + luminance as above. No adjustment is made to the signal to account + for the viewing conditions. + + The reference white level of Windows-scRGB is unknown. If a + reference white level must be assumed for compositor processing, it + should be R=G=B=2.5375 corresponding to 203 cd/m² of Report ITU-R + BT.2408-7. + + The target color volume of Windows-scRGB is unknown. The color gamut + may be anything between sRGB and BT.2100. + + Note: EGL_EXT_gl_colorspace_scrgb_linear definition differs from + Windows-scRGB by using R=G=B=1.0 as the reference white level, while + Windows-scRGB reference white level is unknown or varies. However, + it seems probable that Windows implements both + EGL_EXT_gl_colorspace_scrgb_linear and Vulkan + VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT as Windows-scRGB. + + This request can be used when the compositor advertises + wp_color_manager_v1.feature.windows_scrgb. + Otherwise this request raises the protocol error unsupported_feature. + + The resulting image description object does not allow get_information + request. The wp_image_description_v1.ready event shall be sent. + + + + + + + + When this object is created, it shall immediately send this event once + for each rendering intent the compositor supports. + + A compositor must not advertise intents that are deprecated in the + bound version of the interface. + + + + + + + + When this object is created, it shall immediately send this event once + for each compositor supported feature listed in the enumeration. + + A compositor must not advertise features that are deprecated in the + bound version of the interface. + + + + + + + + When this object is created, it shall immediately send this event once + for each named transfer function the compositor supports with the + parametric image description creator. + + A compositor must not advertise transfer functions that are deprecated + in the bound version of the interface. + + + + + + + + When this object is created, it shall immediately send this event once + for each named set of primaries the compositor supports with the + parametric image description creator. + + A compositor must not advertise names that are deprecated in the + bound version of the interface. + + + + + + + + This event is sent when all supported rendering intents, features, + transfer functions and named primaries have been sent. + + + + + + This request retrieves the image description backing a reference. + + The get_information request can be used if and only if the request that + creates the reference allows it. + + + + + + + + + This creates a pre-defined image description for the so-called + Windows-BT.2100 stimulus encoding. This comes from the Windows 10 + handling of its own definition of a BT.2100 color space for an HDR + screen driven in BT.2100/PQ signalling mode. + + Windows-BT.2100 uses BT.2020 color primaries and white point. + The transfer characteristic is st2084_pq. + + Windows-BT.2100 is generally displayed by Windows 10 without any + adjustments to the signal to account for viewing conditions. + + The reference white level of Windows-BT.2100 is unknown. If a + reference white level must be assumed for compositor processing, it + should be 203 cd/m² of Report ITU-R BT.2408-7. + + The target color volume of Windows-BT.2100 is unknown. The color gamut + may be anything up to BT.2100. + + This request can be used when the compositor advertises + wp_color_manager_v1.feature.windows_bt2100. + Otherwise this request raises the protocol error unsupported_feature. + + The resulting image description object does not allow get_information + request. The wp_image_description_v1.ready event shall be sent. + + + + + + + + + A wp_color_management_output_v1 describes the color properties of an + output. + + The wp_color_management_output_v1 is associated with the wl_output global + underlying the wl_output object. Therefore the client destroying the + wl_output object has no impact, but the compositor removing the output + global makes the wp_color_management_output_v1 object inert. + + + + + Destroy the color wp_color_management_output_v1 object. This does not + affect any remaining protocol objects. + + + + + + This event is sent whenever the image description of the output changed, + followed by one wl_output.done event common to output events across all + extensions. + + If the client wants to use the updated image description, it needs to do + get_image_description again, because image description objects are + immutable. + + + + + + This creates a new wp_image_description_v1 object for the current image + description of the output. There always is exactly one image description + active for an output so the client should destroy the image description + created by earlier invocations of this request. This request is usually + sent as a reaction to the image_description_changed event or when + creating a wp_color_management_output_v1 object. + + The image description of an output represents the color encoding the + output expects. There might be performance and power advantages, as well + as improved color reproduction, if a content update matches the image + description of the output it is being shown on. If a content update is + shown on any other output than the one it matches the image description + of, then the color reproduction on those outputs might be considerably + worse. + + The created wp_image_description_v1 object preserves the image + description of the output from the time the object was created. + + The resulting image description object allows get_information request. + + If this protocol object is inert, the resulting image description object + shall immediately deliver the wp_image_description_v1.failed event with + the no_output cause. + + If the interface version is inadequate for the output's image + description, meaning that the client does not support all the events + needed to deliver the crucial information, the resulting image + description object shall immediately deliver the + wp_image_description_v1.failed event with the low_version cause. + + Otherwise the object shall immediately deliver the ready event. + + + + + + + + + A wp_color_management_surface_v1 allows the client to set the color + space and HDR properties of a surface. + + If the wl_surface associated with the wp_color_management_surface_v1 is + destroyed, the wp_color_management_surface_v1 object becomes inert. + + + + + Destroy the wp_color_management_surface_v1 object and do the same as + unset_image_description. + + + + + + + + + + + + + If this protocol object is inert, the protocol error inert is raised. + + Set the image description of the underlying surface. The image + description and rendering intent are double-buffered state, see + wl_surface.commit. + + It is the client's responsibility to understand the image description + it sets on a surface, and to provide content that matches that image + description. Compositors might convert images to match their own or any + other image descriptions. + + Image descriptions which are not ready (see wp_image_description_v1) + are forbidden in this request, and in such case the protocol error + image_description is raised. + + All image descriptions which are ready (see wp_image_description_v1) + are allowed and must always be accepted by the compositor. + + When an image description is set on a surface, it establishes an + explicit link between surface pixel values and surface colorimetry. + This link may be undefined for some pixel values, see the image + description creator interfaces for the conditions. Non-finite + floating-point values (NaN, Inf) always have an undefined colorimetry. + + A rendering intent provides the client's preference on how surface + colorimetry should be mapped to each output. The render_intent value + must be one advertised by the compositor with + wp_color_manager_v1.render_intent event, otherwise the protocol error + render_intent is raised. + + By default, a surface does not have an associated image description + nor a rendering intent. The handling of color on such surfaces is + compositor implementation defined. Compositors should handle such + surfaces as sRGB, but may handle them differently if they have specific + requirements. + + Setting the image description has copy semantics; after this request, + the image description can be immediately destroyed without affecting + the pending state of the surface. + + + + + + + + + If this protocol object is inert, the protocol error inert is raised. + + This request removes any image description from the surface. See + set_image_description for how a compositor handles a surface without + an image description. This is double-buffered state, see + wl_surface.commit. + + + + + + + A wp_color_management_surface_feedback_v1 allows the client to get the + preferred image description of a surface. + + If the wl_surface associated with this object is destroyed, the + wp_color_management_surface_feedback_v1 object becomes inert. + + + + + Destroy the wp_color_management_surface_feedback_v1 object. + + + + + + + + + + + + Starting from interface version 2, 'preferred_changed2' is sent instead + of this event. See the 'preferred_changed2' event for the definition. + + + + + + + + If this protocol object is inert, the protocol error inert is raised. + + The preferred image description represents the compositor's preferred + color encoding for this wl_surface at the current time. There might be + performance and power advantages, as well as improved color + reproduction, if the image description of a content update matches the + preferred image description. + + This creates a new wp_image_description_v1 object for the currently + preferred image description for the wl_surface. The client should + stop using and destroy the image descriptions created by earlier + invocations of this request for the associated wl_surface. + This request is usually sent as a reaction to the preferred_changed + event or when creating a wp_color_management_surface_feedback_v1 object + if the client is capable of adapting to image descriptions. + + The created wp_image_description_v1 object preserves the preferred image + description of the wl_surface from the time the object was created. + + The resulting image description object allows get_information request. + + If the image description is parametric, the client should set it on its + wl_surface only if the image description is an exact match with the + client content. Particularly if everything else matches, but the target + color volume is greater than what the client needs, the client should + create its own parameric image description with its exact parameters. + + If the interface version is inadequate for the preferred image + description, meaning that the client does not support all the + events needed to deliver the crucial information, the resulting image + description object shall immediately deliver the + wp_image_description_v1.failed event with the low_version cause, + otherwise the object shall immediately deliver the ready event. + + + + + + + + The same description as for get_preferred applies, except the returned + image description is guaranteed to be parametric. This is meant for + clients that can only deal with parametric image descriptions. + + If the compositor doesn't support parametric image descriptions, the + unsupported_feature error is emitted. + + + + + + + + + + The preferred image description is the one which likely has the most + performance and/or quality benefits for the compositor if used by the + client for its wl_surface contents. This event is sent whenever the + compositor changes the wl_surface's preferred image description. + + This event sends the identity of the new preferred state as the argument, + so clients who are aware of the image description already can reuse it. + Otherwise, if the client client wants to know what the preferred image + description is, it shall use the get_preferred request. + + The preferred image description is not automatically used for anything. + It is only a hint, and clients may set any valid image description with + set_image_description, but there might be performance and color accuracy + improvements by providing the wl_surface contents in the preferred + image description. Therefore clients that can, should render according + to the preferred image description + + + + + + + + + + + This type of object is used for collecting all the information required + to create a wp_image_description_v1 object from an ICC file. A complete + set of required parameters consists of these properties: + - ICC file + + Each required property must be set exactly once if the client is to create + an image description. The set requests verify that a property was not + already set. The create request verifies that all required properties are + set. There may be several alternative requests for setting each property, + and in that case the client must choose one of them. + + Once all properties have been set, the create request must be used to + create the image description object, destroying the creator in the + process. + + The link between a pixel value (a device value in ICC) and its respective + colorimetry is defined by the details of the particular ICC profile. + Those details also determine when colorimetry becomes undefined. + + + + + + + + + + + + + + + Create an image description object based on the ICC information + previously set on this object. A compositor must parse the ICC data in + some undefined but finite amount of time. + + The completeness of the parameter set is verified. If the set is not + complete, the protocol error incomplete_set is raised. For the + definition of a complete set, see the description of this interface. + + If the particular combination of the information is not supported + by the compositor, the resulting image description object shall + immediately deliver the wp_image_description_v1.failed event with the + 'unsupported' cause. If a valid image description was created from the + information, the wp_image_description_v1.ready event will eventually + be sent instead. + + This request destroys the wp_image_description_creator_icc_v1 object. + + The resulting image description object does not allow get_information + request. + + + + + + + + Sets the ICC profile file to be used as the basis of the image + description. + + The data shall be found through the given fd at the given offset, having + the given length. The fd must be seekable and readable. Violating these + requirements raises the bad_fd protocol error. + + If reading the data fails due to an error independent of the client, the + compositor shall send the wp_image_description_v1.failed event on the + created wp_image_description_v1 with the 'operating_system' cause. + + The maximum size of the ICC profile is 32 MB. If length is greater than + that or zero, the protocol error bad_size is raised. If offset + length + exceeds the file size, the protocol error out_of_file is raised. + + A compositor may read the file at any time starting from this request + and only until whichever happens first: + - If create request was issued, the wp_image_description_v1 object + delivers either failed or ready event; or + - if create request was not issued, this + wp_image_description_creator_icc_v1 object is destroyed. + + A compositor shall not modify the contents of the file, and the fd may + be sealed for writes and size changes. The client must ensure to its + best ability that the data does not change while the compositor is + reading it. + + The data must represent a valid ICC profile. The ICC profile version + must be 2 or 4, it must be a 3 channel profile and the class must be + Display or ColorSpace. Violating these requirements will not result in a + protocol error, but will eventually send the + wp_image_description_v1.failed event on the created + wp_image_description_v1 with the 'unsupported' cause. + + See the International Color Consortium specification ICC.1:2022 for more + details about ICC profiles. + + If ICC file has already been set on this object, the protocol error + already_set is raised. + + + + + + + + + + + This type of object is used for collecting all the parameters required + to create a wp_image_description_v1 object. A complete set of required + parameters consists of these properties: + - transfer characteristic function (tf) + - chromaticities of primaries and white point (primary color volume) + + The following properties are optional and have a well-defined default + if not explicitly set: + - primary color volume luminance range + - reference white luminance level + - mastering display primaries and white point (target color volume) + - mastering luminance range + + The following properties are optional and will be ignored + if not explicitly set: + - maximum content light level + - maximum frame-average light level + + Each required property must be set exactly once if the client is to create + an image description. The set requests verify that a property was not + already set. The create request verifies that all required properties are + set. There may be several alternative requests for setting each property, + and in that case the client must choose one of them. + + Once all properties have been set, the create request must be used to + create the image description object, destroying the creator in the + process. + + A viewer, who is viewing the display defined by the resulting image + description (the viewing environment included), is assumed to be fully + adapted to the primary color volume's white point. + + Any of the following conditions will cause the colorimetry of a pixel + to become undefined: + - Values outside of the defined range of the transfer characteristic. + - Tristimulus that exceeds the target color volume. + - If extended_target_volume is not supported: tristimulus that exceeds + the primary color volume. + + The closest correspondence to an image description created through this + interface is the Display class of profiles in ICC. + + + + + + + + + + + + + + + + Create an image description object based on the parameters previously + set on this object. + + The completeness of the parameter set is verified. If the set is not + complete, the protocol error incomplete_set is raised. For the + definition of a complete set, see the description of this interface. + + When both max_cll and max_fall are set, max_fall must be less or equal + to max_cll otherwise the invalid_luminance protocol error is raised. + + In version 1, these following conditions also result in the + invalid_luminance protocol error. Version 2 and later do not have this + requirement. + - When max_cll is set, it must be greater than min L and less or equal + to max L of the mastering luminance range. + - When max_fall is set, it must be greater than min L and less or equal + to max L of the mastering luminance range. + + If the particular combination of the parameter set is not supported + by the compositor, the resulting image description object shall + immediately deliver the wp_image_description_v1.failed event with the + 'unsupported' cause. If a valid image description was created from the + parameter set, the wp_image_description_v1.ready event will eventually + be sent instead. + + This request destroys the wp_image_description_creator_params_v1 + object. + + The resulting image description object does not allow get_information + request. + + + + + + + + Sets the transfer characteristic using explicitly enumerated named + functions. + + When the resulting image description is attached to an image, the + content should be decoded according to the industry standard + practices for the transfer characteristic. + + Only names advertised with wp_color_manager_v1 event supported_tf_named + are allowed. Other values shall raise the protocol error invalid_tf. + + If transfer characteristic has already been set on this object, the + protocol error already_set is raised. + + + + + + + + Sets the color component transfer characteristic to a power curve with + the given exponent. Negative values are handled by mirroring the + positive half of the curve through the origin. The valid domain and + range of the curve are all finite real numbers. This curve represents + the conversion from electrical to optical color channel values. + + The curve exponent shall be multiplied by 10000 to get the argument eexp + value to carry the precision of 4 decimals. + + The curve exponent must be at least 1.0 and at most 10.0. Otherwise the + protocol error invalid_tf is raised. + + If transfer characteristic has already been set on this object, the + protocol error already_set is raised. + + This request can be used when the compositor advertises + wp_color_manager_v1.feature.set_tf_power. Otherwise this request raises + the protocol error unsupported_feature. + + + + + + + + Sets the color primaries and white point using explicitly named sets. + This describes the primary color volume which is the basis for color + value encoding. + + Only names advertised with wp_color_manager_v1 event + supported_primaries_named are allowed. Other values shall raise the + protocol error invalid_primaries_named. + + If primaries have already been set on this object, the protocol error + already_set is raised. + + + + + + + + Sets the color primaries and white point using CIE 1931 xy chromaticity + coordinates. This describes the primary color volume which is the basis + for color value encoding. + + Each coordinate value is multiplied by 1 million to get the argument + value to carry precision of 6 decimals. + + If primaries have already been set on this object, the protocol error + already_set is raised. + + This request can be used if the compositor advertises + wp_color_manager_v1.feature.set_primaries. Otherwise this request raises + the protocol error unsupported_feature. + + + + + + + + + + + + + + + Sets the primary color volume luminance range and the reference white + luminance level. These values include the minimum display emission, but + not external flare. The minimum display emission is assumed to have + the chromaticity of the primary color volume white point. + + The default luminances from + https://www.color.org/chardata/rgb/srgb.xalter are + - primary color volume minimum: 0.2 cd/m² + - primary color volume maximum: 80 cd/m² + - reference white: 80 cd/m² + + Setting a named transfer characteristic can imply other default + luminances. + + The default luminances get overwritten when this request is used. + With transfer_function.st2084_pq the given 'max_lum' value is ignored, + and 'max_lum' is taken as 'min_lum' + 10000 cd/m². + + 'min_lum' and 'max_lum' specify the minimum and maximum luminances of + the primary color volume as reproduced by the targeted display. + + 'reference_lum' specifies the luminance of the reference white as + reproduced by the targeted display, and reflects the targeted viewing + environment. + + Compositors should make sure that all content is anchored, meaning that + an input signal level of 'reference_lum' on one image description and + another input signal level of 'reference_lum' on another image + description should produce the same output level, even though the + 'reference_lum' on both image representations can be different. + + 'reference_lum' may be higher than 'max_lum'. In that case reaching + the reference white output level in image content requires the + 'extended_target_volume' feature support. + + If 'max_lum' or 'reference_lum' are less than or equal to 'min_lum', + the protocol error invalid_luminance is raised. + + The minimum luminance is multiplied by 10000 to get the argument + 'min_lum' value and carries precision of 4 decimals. The maximum + luminance and reference white luminance values are unscaled. + + If the primary color volume luminance range and the reference white + luminance level have already been set on this object, the protocol error + already_set is raised. + + This request can be used if the compositor advertises + wp_color_manager_v1.feature.set_luminances. Otherwise this request + raises the protocol error unsupported_feature. + + + + + + + + + + Provides the color primaries and white point of the mastering display + using CIE 1931 xy chromaticity coordinates. This is compatible with the + SMPTE ST 2086 definition of HDR static metadata. + + The mastering display primaries and mastering display luminances define + the target color volume. + + If mastering display primaries are not explicitly set, the target color + volume is assumed to have the same primaries as the primary color volume. + + The target color volume is defined by all tristimulus values between 0.0 + and 1.0 (inclusive) of the color space defined by the given mastering + display primaries and white point. The colorimetry is identical between + the container color space and the mastering display color space, + including that no chromatic adaptation is applied even if the white + points differ. + + The target color volume can exceed the primary color volume to allow for + a greater color volume with an existing color space definition (for + example scRGB). It can be smaller than the primary color volume to + minimize gamut and tone mapping distances for big color spaces (HDR + metadata). + + To make use of the entire target color volume a suitable pixel format + has to be chosen (e.g. floating point to exceed the primary color + volume, or abusing limited quantization range as with xvYCC). + + Each coordinate value is multiplied by 1 million to get the argument + value to carry precision of 6 decimals. + + If mastering display primaries have already been set on this object, the + protocol error already_set is raised. + + This request can be used if the compositor advertises + wp_color_manager_v1.feature.set_mastering_display_primaries. Otherwise + this request raises the protocol error unsupported_feature. The + advertisement implies support only for target color volumes fully + contained within the primary color volume. + + If a compositor additionally supports target color volume exceeding the + primary color volume, it must advertise + wp_color_manager_v1.feature.extended_target_volume. If a client uses + target color volume exceeding the primary color volume and the + compositor does not support it, the result is implementation defined. + Compositors are recommended to detect this case and fail the image + description gracefully, but it may as well result in color artifacts. + + + + + + + + + + + + + + + Sets the luminance range that was used during the content mastering + process as the minimum and maximum absolute luminance L. These values + include the minimum display emission and ambient flare luminances, + assumed to be optically additive and have the chromaticity of the + primary color volume white point. This should be + compatible with the SMPTE ST 2086 definition of HDR static metadata. + + The mastering display primaries and mastering display luminances define + the target color volume. + + If mastering luminances are not explicitly set, the target color volume + is assumed to have the same min and max luminances as the primary color + volume. + + If max L is less than or equal to min L, the protocol error + invalid_luminance is raised. + + Min L value is multiplied by 10000 to get the argument min_lum value + and carry precision of 4 decimals. Max L value is unscaled for max_lum. + + This request can be used if the compositor advertises + wp_color_manager_v1.feature.set_mastering_display_primaries. Otherwise + this request raises the protocol error unsupported_feature. The + advertisement implies support only for target color volumes fully + contained within the primary color volume. + + If a compositor additionally supports target color volume exceeding the + primary color volume, it must advertise + wp_color_manager_v1.feature.extended_target_volume. If a client uses + target color volume exceeding the primary color volume and the + compositor does not support it, the result is implementation defined. + Compositors are recommended to detect this case and fail the image + description gracefully, but it may as well result in color artifacts. + + + + + + + + + Sets the maximum content light level (max_cll) as defined by CTA-861-H. + + max_cll is undefined by default. + + + + + + + + Sets the maximum frame-average light level (max_fall) as defined by + CTA-861-H. + + max_fall is undefined by default. + + + + + + + + + An image description carries information about the pixel color encoding + and its intended display and viewing environment. The image description is + attached to a wl_surface via + wp_color_management_surface_v1.set_image_description. A compositor can use + this information to decode pixel values into colorimetrically meaningful + quantities, which allows the compositor to transform the surface contents + to become suitable for various displays and viewing environments. + + Note, that the wp_image_description_v1 object is not ready to be used + immediately after creation. The object eventually delivers either the + 'ready' or the 'failed' event, specified in all requests creating it. The + object is deemed "ready" after receiving the 'ready' event. + + An object which is not ready is illegal to use, it can only be destroyed. + Any other request in this interface shall result in the 'not_ready' + protocol error. Attempts to use an object which is not ready through other + interfaces shall raise protocol errors defined there. + + Once created and regardless of how it was created, a + wp_image_description_v1 object always refers to one fixed image + description. It cannot change after creation. + + + + + Destroy this object. It is safe to destroy an object which is not ready. + + Destroying a wp_image_description_v1 object has no side-effects, not + even if a wp_color_management_surface_v1.set_image_description has not + yet been followed by a wl_surface.commit. + + + + + + + + + + + + + + + + + + + + + + If creating a wp_image_description_v1 object fails for a reason that is + not defined as a protocol error, this event is sent. + + The requests that create image description objects define whether and + when this can occur. Only such creation requests can trigger this event. + This event cannot be triggered after the image description was + successfully formed. + + Once this event has been sent, the wp_image_description_v1 object will + never become ready and it can only be destroyed. + + + + + + + + + Starting from interface version 2, the 'ready2' event is sent instead + of this event. + + For the definition of this event, see the 'ready2' event. The + difference to this event is as follows. + + The id number is valid only as long as the protocol object is alive. If + all protocol objects referring to the same image description record are + destroyed, the id number may be recycled for a different image + description record. + + + + + + + + Creates a wp_image_description_info_v1 object which delivers the + information that makes up the image description. + + Not all image description protocol objects allow get_information + request. Whether it is allowed or not is defined by the request that + created the object. If get_information is not allowed, the protocol + error no_information is raised. + + + + + + + + + + Once this event has been sent, the wp_image_description_v1 object is + deemed "ready". Ready objects can be used to send requests and can be + used through other interfaces. + + Every ready wp_image_description_v1 protocol object refers to an + underlying image description record in the compositor. Multiple protocol + objects may end up referring to the same record. Clients may identify + these "copies" by comparing their id numbers: if the numbers from two + protocol objects are identical, the protocol objects refer to the same + image description record. Two different image description records + cannot have the same id number simultaneously. The id number does not + change during the lifetime of the image description record. + + Image description id number is not a protocol object id. Zero is + reserved as an invalid id number. It shall not be possible for a client + to refer to an image description by its id number in protocol. The id + numbers might not be portable between Wayland connections. A compositor + shall not send an invalid id number. + + Compositors must not recycle image description id numbers. + + This identity allows clients to de-duplicate image description records + and avoid get_information request if they already have the image + description information. + + + + + + + + + + Sends all matching events describing an image description object exactly + once and finally sends the 'done' event. + + This means + - if the image description is parametric, it must send + - primaries + - named_primaries, if applicable + - at least one of tf_power and tf_named, as applicable + - luminances + - target_primaries + - target_luminance + - if the image description is parametric, it may send, if applicable, + - target_max_cll + - target_max_fall + - if the image description contains an ICC profile, it must send the + icc_file event + + Once a wp_image_description_info_v1 object has delivered a 'done' event it + is automatically destroyed. + + Every wp_image_description_info_v1 created from the same + wp_image_description_v1 shall always return the exact same data. + + + + + Signals the end of information events and destroys the object. + + + + + + The icc argument provides a file descriptor to the client which may be + memory-mapped to provide the ICC profile matching the image description. + The fd is read-only, and if mapped then it must be mapped with + MAP_PRIVATE by the client. + + The ICC profile version and other details are determined by the + compositor. There is no provision for a client to ask for a specific + kind of a profile. + + + + + + + + + + Delivers the primary color volume primaries and white point using CIE + 1931 xy chromaticity coordinates. + + Each coordinate value is multiplied by 1 million to get the argument + value to carry precision of 6 decimals. + + + + + + + + + + + + + + + Delivers the primary color volume primaries and white point using an + explicitly enumerated named set. + + + + + + + + The color component transfer characteristic of this image description is + a pure power curve. This event provides the exponent of the power + function. This curve represents the conversion from electrical to + optical pixel or color values. + + The curve exponent has been multiplied by 10000 to get the argument eexp + value to carry the precision of 4 decimals. + + + + + + + + Delivers the transfer characteristic using an explicitly enumerated + named function. + + + + + + + + Delivers the primary color volume luminance range and the reference + white luminance level. These values include the minimum display emission + and ambient flare luminances, assumed to be optically additive and have + the chromaticity of the primary color volume white point. + + The minimum luminance is multiplied by 10000 to get the argument + 'min_lum' value and carries precision of 4 decimals. The maximum + luminance and reference white luminance values are unscaled. + + + + + + + + + + Provides the color primaries and white point of the target color volume + using CIE 1931 xy chromaticity coordinates. This is compatible with the + SMPTE ST 2086 definition of HDR static metadata for mastering displays. + + While primary color volume is about how color is encoded, the target + color volume is the actually displayable color volume. + + Each coordinate value is multiplied by 1 million to get the argument + value to carry precision of 6 decimals. + + + + + + + + + + + + + + + Provides the luminance range that the image description is targeting as + the minimum and maximum absolute luminance L. These values include the + minimum display emission and ambient flare luminances, assumed to be + optically additive and have the chromaticity of the primary color + volume white point. This should be compatible with the SMPTE ST 2086 + definition of HDR static metadata. + + This luminance range is only theoretical and may not correspond to the + luminance of light emitted on an actual display. + + Min L value is multiplied by 10000 to get the argument min_lum value and + carry precision of 4 decimals. Max L value is unscaled for max_lum. + + + + + + + + + Provides the targeted max_cll of the image description. max_cll is + defined by CTA-861-H. + + This luminance is only theoretical and may not correspond to the + luminance of light emitted on an actual display. + + + + + + + + Provides the targeted max_fall of the image description. max_fall is + defined by CTA-861-H. + + This luminance is only theoretical and may not correspond to the + luminance of light emitted on an actual display. + + + + + + + + + This object is a reference to an image description. This interface is + frozen at version 1 to allow other protocols to create + wp_image_description_v1 objects. + + The wp_color_manager_v1.get_image_description request can be used to + retrieve the underlying image description. + + + + + Destroy this object. This has no effect on the referenced image + description. + + + + -- GitLab