diff --git a/include/libweston/libweston.h b/include/libweston/libweston.h index 9d0ba6def..667b79ea2 100644 --- a/include/libweston/libweston.h +++ b/include/libweston/libweston.h @@ -410,6 +410,12 @@ struct weston_head { /** Current content protection status */ enum weston_hdcp_protection current_protection; + + /* xx_color_manager_v1.get_color_management_output + * + * When a client uses this request, we add the wl_resource we create to + * this list. */ + struct wl_list cm_output_resource_list; }; /** Output properties derived from its color characteristics and profile @@ -1804,6 +1810,11 @@ struct weston_surface_state { /* weston_protected_surface.enforced/relaxed */ enum weston_surface_protection_mode protection_mode; + + /* color_management_surface_v1_interface.set_image_description or + * color_management_surface_v1_interface.unset_image_description */ + struct weston_color_profile *color_profile; + const struct weston_render_intent_info *render_intent; }; struct weston_surface_activation_data { @@ -1949,6 +1960,16 @@ struct weston_surface { enum weston_surface_protection_mode protection_mode; struct weston_tearing_control *tear_control; + + struct weston_color_profile *color_profile; + struct weston_color_profile *preferred_color_profile; + const struct weston_render_intent_info *render_intent; + + /* xx_color_manager_v1.get_color_management_surface + * + * When a client uses this request, we add the wl_resource we create to + * this list. */ + struct wl_list cm_surface_resource_list; }; struct weston_subsurface { @@ -2190,6 +2211,11 @@ weston_compositor_set_default_pointer_grab(struct weston_compositor *compositor, struct weston_surface * weston_surface_create(struct weston_compositor *compositor); +void +weston_surface_set_color_profile(struct weston_surface *surface, + struct weston_color_profile *cprof, + const struct weston_render_intent_info *intent_info); + struct weston_view * weston_view_create(struct weston_surface *surface); diff --git a/libweston/color-lcms/color-lcms.c b/libweston/color-lcms/color-lcms.c index 6356fe1f7..b20603a70 100644 --- a/libweston/color-lcms/color-lcms.c +++ b/libweston/color-lcms/color-lcms.c @@ -35,6 +35,7 @@ #include "color-properties.h" #include "shared/helpers.h" #include "shared/xalloc.h" +#include "shared/weston-assert.h" const char * cmlcms_category_name(enum cmlcms_category cat) @@ -56,17 +57,22 @@ cmlcms_get_render_intent(enum cmlcms_category cat, struct weston_surface *surface, struct weston_output *output) { - const struct weston_render_intent_info *render_intent; + struct weston_color_manager *cm = output->compositor->color_manager; - /* - * TODO: Take into account client provided content profile, - * output profile, and the category of the wanted color - * transformation. - */ - render_intent = - weston_render_intent_info_from(output->compositor, - WESTON_RENDER_INTENT_RELATIVE); - return render_intent; + /* TODO: take into account the cmlcms_category. */ + + /* Use default render intent. TODO: default should be + * WESTON_RENDER_INTENT_PERCEPTUAL. That requires tweaking the tests. */ + if (!surface || !surface->render_intent) { + weston_assert_true(output->compositor, (cm->supported_rendering_intents >> + WESTON_RENDER_INTENT_RELATIVE) & 1); + + return weston_render_intent_info_from(output->compositor, + WESTON_RENDER_INTENT_RELATIVE); + } + + weston_assert_ptr(surface->compositor, surface->color_profile); + return surface->render_intent; } static struct cmlcms_color_profile * @@ -100,7 +106,7 @@ cmlcms_get_surface_color_transform(struct weston_color_manager *cm_base, struct cmlcms_color_transform_search_param param = { .category = CMLCMS_CATEGORY_INPUT_TO_BLEND, - .input_profile = get_cprof_or_stock_sRGB(cm, NULL /* TODO: surface->color_profile */), + .input_profile = get_cprof_or_stock_sRGB(cm, surface->color_profile), .output_profile = get_cprof_or_stock_sRGB(cm, output->color_profile), }; param.render_intent = cmlcms_get_render_intent(param.category, @@ -373,10 +379,30 @@ cmlcms_destroy(struct weston_color_manager *cm_base) struct weston_color_manager_lcms *cm = get_cmlcms(cm_base); if (cm->sRGB_profile) { - assert(cm->sRGB_profile->base.ref_count == 1); + /* TODO: when we fix the ugly bug described below, we should + * change this assert to == 1. */ + weston_assert_true(cm->base.compositor, + cm->sRGB_profile->base.ref_count >= 1); unref_cprof(cm->sRGB_profile); } + /* TODO: this is an ugly hack. Remove it when we stop leaking surfaces + * when shutting down Weston with client surfaces alive. */ + if (!wl_list_empty(&cm->color_profile_list)) { + struct cmlcms_color_profile *cprof, *tmp; + + weston_log("BUG: When Weston is shutting down with client surfaces alive, it may\n" \ + "leak them. This is a bug that needs to be fixed. At this point (in which\n" \ + "we are destroying the color manager), we expect all the objects referencing\n" \ + "color profiles to be already gone and, consequently, the color profiles\n" \ + "themselves should have been already destroyed. But because of this other\n" \ + "bug, this didn't happen, and now we destroy the color profiles and leave\n" \ + "dangling pointers around."); + + wl_list_for_each_safe(cprof, tmp, &cm->color_profile_list, link) + cmlcms_color_profile_destroy(cprof); + } + assert(wl_list_empty(&cm->color_transform_list)); assert(wl_list_empty(&cm->color_profile_list)); @@ -450,10 +476,21 @@ weston_color_manager_create(struct weston_compositor *compositor) cm->base.destroy_color_profile = cmlcms_destroy_color_profile; cm->base.get_stock_sRGB_color_profile = cmlcms_get_stock_sRGB_color_profile; cm->base.get_color_profile_from_icc = cmlcms_get_color_profile_from_icc; + cm->base.send_image_desc_info = cmlcms_send_image_desc_info; cm->base.destroy_color_transform = cmlcms_destroy_color_transform; cm->base.get_surface_color_transform = cmlcms_get_surface_color_transform; cm->base.create_output_color_outcome = cmlcms_create_output_color_outcome; + /* We still do not support creating parametric color profiles. */ + cm->base.supported_color_features = (1 << WESTON_COLOR_FEATURE_ICC); + + /* We support all rendering intents. */ + cm->base.supported_rendering_intents = (1 << WESTON_RENDER_INTENT_PERCEPTUAL) | + (1 << WESTON_RENDER_INTENT_SATURATION) | + (1 << WESTON_RENDER_INTENT_ABSOLUTE) | + (1 << WESTON_RENDER_INTENT_RELATIVE) | + (1 << WESTON_RENDER_INTENT_RELATIVE_BPC); + wl_list_init(&cm->color_transform_list); wl_list_init(&cm->color_profile_list); diff --git a/libweston/color-lcms/color-lcms.h b/libweston/color-lcms/color-lcms.h index 12d767017..c199cb713 100644 --- a/libweston/color-lcms/color-lcms.h +++ b/libweston/color-lcms/color-lcms.h @@ -33,6 +33,7 @@ #include "color.h" #include "shared/helpers.h" +#include "shared/os-compatibility.h" struct weston_color_manager_lcms { struct weston_color_manager base; @@ -65,6 +66,9 @@ struct cmlcms_color_profile { cmsHPROFILE profile; struct cmlcms_md5_sum md5sum; + /* Only for profiles created from an ICC file. */ + struct ro_anonymous_file *prof_rofile; + /** The curves to decode an electrical signal * * For ICC profiles, if the profile type is matrix-shaper, then eotf @@ -138,6 +142,10 @@ cmlcms_get_color_profile_from_icc(struct weston_color_manager *cm, struct weston_color_profile **cprof_out, char **errmsg); +bool +cmlcms_send_image_desc_info(struct cm_image_desc_info *cm_image_desc_info, + struct weston_color_profile *cprof_base); + void cmlcms_destroy_color_profile(struct weston_color_profile *cprof_base); diff --git a/libweston/color-lcms/color-profile.c b/libweston/color-lcms/color-profile.c index 00e0ac5d9..c1a051233 100644 --- a/libweston/color-lcms/color-profile.c +++ b/libweston/color-lcms/color-profile.c @@ -26,16 +26,17 @@ #include "config.h" -#include #include #include #include #include "color.h" #include "color-lcms.h" +#include "color-management.h" #include "shared/helpers.h" #include "shared/string-helpers.h" #include "shared/xalloc.h" +#include "shared/weston-assert.h" struct xyz_arr_flt { float v[3]; @@ -350,6 +351,10 @@ cmlcms_color_profile_destroy(struct cmlcms_color_profile *cprof) cmsFreeToneCurveTriple(cprof->output_inv_eotf_vcgt); cmsCloseProfile(cprof->profile); + /* Only profiles created from ICC files have these. */ + if (cprof->prof_rofile) + os_ro_anonymous_file_destroy(cprof->prof_rofile); + weston_log_scope_printf(cm->profiles_scope, "Destroyed color profile %p. " \ "Description: %s\n", cprof, cprof->base.description); @@ -464,7 +469,7 @@ cmlcms_get_color_profile_from_icc(struct weston_color_manager *cm_base, struct weston_color_manager_lcms *cm = get_cmlcms(cm_base); cmsHPROFILE profile; struct cmlcms_md5_sum md5sum; - struct cmlcms_color_profile *cprof; + struct cmlcms_color_profile *cprof = NULL; char *desc = NULL; if (!icc_data || icc_len < 1) { @@ -506,15 +511,85 @@ cmlcms_get_color_profile_from_icc(struct weston_color_manager *cm_base, if (!cprof) goto err_close; + cprof->prof_rofile = os_ro_anonymous_file_create(icc_len, icc_data); + if (!cprof->prof_rofile) + goto err_close; + *cprof_out = &cprof->base; return true; err_close: + if (cprof) + cmlcms_color_profile_destroy(cprof); free(desc); cmsCloseProfile(profile); return false; } +bool +cmlcms_send_image_desc_info(struct cm_image_desc_info *cm_image_desc_info, + struct weston_color_profile *cprof_base) +{ + struct weston_color_manager_lcms *cm = get_cmlcms(cprof_base->cm); + struct weston_compositor *compositor = cm->base.compositor; + struct cmlcms_color_profile *cprof = get_cprof(cprof_base); + const struct weston_color_primaries_info *primaries_info; + const struct weston_color_tf_info *tf_info; + int32_t fd; + uint32_t len; + + if (cprof->prof_rofile) { + /* ICC-based color profile, so just send the ICC file fd. If we + * get an error (negative fd), the helper will send the proper + * error to the client. */ + fd = os_ro_anonymous_file_get_fd(cprof->prof_rofile, + RO_ANONYMOUS_FILE_MAPMODE_PRIVATE); + if (fd < 0) { + weston_cm_send_icc_file(cm_image_desc_info, -1, 0); + return false; + } + + len = os_ro_anonymous_file_size(cprof->prof_rofile); + weston_assert_uint32_gt(compositor, len, 0); + + weston_cm_send_icc_file(cm_image_desc_info, fd, len); + + os_ro_anonymous_file_put_fd(fd); + } else { + /* TODO: we still don't support parametric color profiles that + * are not the stock one. This should change when we start + * advertising parametric image description support in our + * color-management protocol implementation. */ + if (cprof != cm->sRGB_profile) + weston_assert_not_reached(compositor, "we don't support parametric " \ + "cprof's that are not the stock sRGB one"); + + /* Stock sRGB color profile. TODO: when we add support for + * parametric color profiles, the stock sRGB will be crafted + * using parameters, instead of cmsCreate_sRGBProfileTHR() + * (which we currently use). So we'll get the parameters + * directly from it, instead of hardcoding as we are doing here. + * We don't get the parameters from the stock sRGB color profile + * because it is not trivial to retrieve that from LittleCMS. */ + + /* Send the H.273 ColourPrimaries code point that matches the + * Rec709 primaries and the D65 white point. */ + primaries_info = weston_color_primaries_info_from(compositor, + WESTON_PRIMARIES_CICP_SRGB); + weston_cm_send_primaries_named(cm_image_desc_info, primaries_info); + + /* These are the Rec709 primaries and D65 white point. */ + weston_cm_send_primaries(cm_image_desc_info, + &primaries_info->color_gamut); + + /* sRGB transfer function. */ + tf_info = weston_color_tf_info_from(compositor, WESTON_TF_GAMMA22); + weston_cm_send_tf_named(cm_image_desc_info, tf_info); + } + + return true; +} + void cmlcms_destroy_color_profile(struct weston_color_profile *cprof_base) { diff --git a/libweston/color-lcms/meson.build b/libweston/color-lcms/meson.build index 920f8420e..41dbaed9d 100644 --- a/libweston/color-lcms/meson.build +++ b/libweston/color-lcms/meson.build @@ -16,6 +16,7 @@ config_h.set10('HAVE_CMS_GET_TONE_CURVE_SEGMENT', has_function_cmsGetToneCurveSegment) srcs_color_lcms = [ + color_management_v1_server_protocol_h, 'color-lcms.c', 'color-profile.c', 'color-transform.c', @@ -28,6 +29,7 @@ deps_color_lcms = [ dep_libm, dep_libweston_private, dep_lcms2, + dep_libshared, ] plugin_color_lcms = shared_library( diff --git a/libweston/color-management.c b/libweston/color-management.c new file mode 100644 index 000000000..c757c0d14 --- /dev/null +++ b/libweston/color-management.c @@ -0,0 +1,1119 @@ +/* + * Copyright 2023 Collabora, Ltd. + * + * 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. + */ + +#include "config.h" + +#include "color.h" +#include "color-management.h" +#include "shared/string-helpers.h" +#include "shared/weston-assert.h" +#include "shared/xalloc.h" + +#include + +#include "color-management-v1-server-protocol.h" + +enum supports_get_info { + NO_GET_INFO = false, + YES_GET_INFO = true, +}; + +/** + * This is the object that backs the image description abstraction from the + * protocol. We may have multiple images descriptions for the same color + * profile. + */ +struct cm_image_desc { + struct wl_resource *owner; + struct weston_color_manager *cm; + + /* Reference to the color profile that it is backing up. */ + struct weston_color_profile *cprof; + + /* Depending how the image description is created, the protocol states + * that get_information() request should be invalid. */ + bool supports_get_info; +}; + +/** + * Object created when get_info() is called for an image description object. It + * gets destroyed when all the info is sent, i.e. with the done() event. + */ +struct cm_image_desc_info { + struct wl_resource *owner; + struct weston_compositor *compositor; +}; + +/** + * When clients want to create image description based on ICC color profiles, we + * use this struct to help. + */ +struct cm_creator_icc { + struct wl_resource *owner; + + struct weston_compositor *compositor; + + /* ICC profile data given by the client. */ + int32_t icc_profile_fd; + size_t icc_data_length; + size_t icc_data_offset; +}; + +/** + * For an ICC-based image description, sends the ICC information to the + * client. + * + * If callers fail to create the fd for the ICC, they can call this function + * with fd == -1 and it should return the proper error to clients. + * + * This is a helper function that should be used by the color plugin + * that owns the color profile and has information about it. + * + * \param cm_image_desc_info The image description info object + * \param fd The ICC profile file descriptor, or -1 in case of a failure + * \param len The ICC profile size, in bytes + */ +WL_EXPORT void +weston_cm_send_icc_file(struct cm_image_desc_info *cm_image_desc_info, + int32_t fd, uint32_t len) +{ + /* Caller failed to create fd. At this point we already know that the + * ICC is valid, so let's disconnect the client with OOM. */ + if (fd < 0) { + wl_resource_post_no_memory(cm_image_desc_info->owner); + return; + } + + xx_image_description_info_v2_send_icc_file(cm_image_desc_info->owner, + fd, len); +} + +/** + * For a parametric image description, sends its + * enum xx_color_manager_v2_primaries code to the client. + * + * This is a helper function that should be used by the color plugin + * that owns the color profile and has information about it. + * + * \param cm_image_desc_info The image description info object + * \param primaries_info The primaries_info object + */ +WL_EXPORT void +weston_cm_send_primaries_named(struct cm_image_desc_info *cm_image_desc_info, + const struct weston_color_primaries_info *primaries_info) +{ + xx_image_description_info_v2_send_primaries_named(cm_image_desc_info->owner, + primaries_info->protocol_primaries); +} + +/** + * For a parametric image description, sends the primary color volume primaries + * and white point using CIE 1931 xy chromaticity coordinates to the client. + * + * This is a helper function that should be used by the color plugin + * that owns the color profile and has information about it. + * + * \param cm_image_desc_info The image description info object + * \param color_gamut The CIE 1931 xy chromaticity coordinates + */ +WL_EXPORT void +weston_cm_send_primaries(struct cm_image_desc_info *cm_image_desc_info, + const struct weston_color_gamut *color_gamut) +{ + xx_image_description_info_v2_send_primaries(cm_image_desc_info->owner, + /* red */ + round(color_gamut->primary[0].x * 10000), + round(color_gamut->primary[0].y * 10000), + /* green */ + round(color_gamut->primary[1].x * 10000), + round(color_gamut->primary[1].y * 10000), + /* blue */ + round(color_gamut->primary[2].x * 10000), + round(color_gamut->primary[2].y * 10000), + /* white point */ + round(color_gamut->white_point.x * 10000), + round(color_gamut->white_point.y * 10000)); +} + +/** + * For a parametric image description, sends its + * enum xx_color_manager_v2_transfer_function code to the client. + * + * This is a helper function that should be used by the color plugin + * that owns the color profile and has information about it. + * + * \param cm_image_desc_info The image description info object + * \param tf_info The tf_info object + */ +WL_EXPORT void +weston_cm_send_tf_named(struct cm_image_desc_info *cm_image_desc_info, + const struct weston_color_tf_info *tf_info) +{ + xx_image_description_info_v2_send_tf_named(cm_image_desc_info->owner, + tf_info->protocol_tf); +} + +/** + * Destroy an image description info object. + */ +static void +cm_image_desc_info_destroy(struct cm_image_desc_info *cm_image_desc_info) +{ + free(cm_image_desc_info); +} + +/** + * Resource destruction function for the image description info. Destroys the + * image description info backing object. + */ +static void +image_description_info_resource_destroy(struct wl_resource *cm_image_desc_info_res) +{ + struct cm_image_desc_info *cm_image_desc_info = + wl_resource_get_user_data(cm_image_desc_info_res); + + cm_image_desc_info_destroy(cm_image_desc_info); +} + +/** + * Creates object to send information of a certain image description. + */ +static struct cm_image_desc_info * +image_description_info_create(struct wl_client *client, uint32_t version, + struct weston_compositor *compositor, + uint32_t cm_image_desc_info_id) +{ + struct cm_image_desc_info *cm_image_desc_info; + + cm_image_desc_info = xzalloc(sizeof(*cm_image_desc_info)); + + cm_image_desc_info->compositor = compositor; + + cm_image_desc_info->owner = + wl_resource_create(client, &xx_image_description_info_v2_interface, + version, cm_image_desc_info_id); + if (!cm_image_desc_info->owner) { + free(cm_image_desc_info); + return NULL; + } + + wl_resource_set_implementation(cm_image_desc_info->owner, + NULL, cm_image_desc_info, + image_description_info_resource_destroy); + + return cm_image_desc_info; +} + +/** + * Client wants the image description information. + */ +static void +image_description_get_information(struct wl_client *client, + struct wl_resource *cm_image_desc_res, + uint32_t cm_image_desc_info_id) +{ + struct cm_image_desc *cm_image_desc = + wl_resource_get_user_data(cm_image_desc_res); + uint32_t version = wl_resource_get_version(cm_image_desc_res); + struct cm_image_desc_info *cm_image_desc_info; + bool success; + + /* If the client tries to get the output image description but we + * destroy the output before that, we create the image description but + * without a backing struct cm_image_desc object. In such case, the only + * request the client can make is the one to destroy the object. */ + if (!cm_image_desc) { + wl_resource_post_error(cm_image_desc_res, + XX_IMAGE_DESCRIPTION_V2_ERROR_NOT_READY, + "we didn't send ready so client is not allowed " + "to get_information for this object"); + return; + } + + /* Depending how the image description is created, the protocol states + * that get_information() request should be invalid. */ + if (!cm_image_desc->supports_get_info) { + wl_resource_post_error(cm_image_desc_res, + XX_IMAGE_DESCRIPTION_V2_ERROR_NO_INFORMATION, + "get_information is not allowed for this " + "image description"); + return; + } + + /* Create object responsible for sending the image description info. */ + cm_image_desc_info = + image_description_info_create(client, version, + cm_image_desc->cm->compositor, + cm_image_desc_info_id); + if (!cm_image_desc_info) { + wl_resource_post_no_memory(cm_image_desc_res); + return; + } + + /* The color plugin is the one that has information about the color + * profile, so we go through it to send the info to clients. It uses + * our helpers (weston_cm_send_primaries(), etc) to do that. */ + success = cm_image_desc->cm->send_image_desc_info(cm_image_desc_info, + cm_image_desc->cprof); + if (success) + xx_image_description_info_v2_send_done(cm_image_desc_info->owner); + + /* All info sent, so destroy the object. */ + wl_resource_destroy(cm_image_desc_info->owner); +} + +/** + * Client will not use the image description anymore, so we destroy its + * resource. + */ +static void +image_description_destroy(struct wl_client *client, + struct wl_resource *cm_image_desc_res) +{ + wl_resource_destroy(cm_image_desc_res); +} + +static void +cm_image_desc_destroy(struct cm_image_desc *cm_image_desc); + +/** + * Resource destruction function for the image description. Destroys the image + * description backing object. + */ +static void +image_description_resource_destroy(struct wl_resource *cm_image_desc_res) +{ + struct cm_image_desc *cm_image_desc = + wl_resource_get_user_data(cm_image_desc_res); + + /* If the client tries to get an output image description but we destroy + * the output before that, we create the image description but without a + * backing struct cm_image_desc object. */ + if (!cm_image_desc) + return; + + cm_image_desc_destroy(cm_image_desc); +} + +static const struct xx_image_description_v2_interface +image_description_implementation = { + .destroy = image_description_destroy, + .get_information = image_description_get_information, +}; + +/** + * Creates an image description object for a certain color profile. + */ +static struct cm_image_desc * +cm_image_desc_create(struct weston_color_manager *cm, + struct weston_color_profile *cprof, + struct wl_client *client, uint32_t version, + uint32_t image_description_id, + enum supports_get_info supports_get_info) +{ + struct cm_image_desc *cm_image_desc; + + cm_image_desc = xzalloc(sizeof(*cm_image_desc)); + + cm_image_desc->owner = + wl_resource_create(client, &xx_image_description_v2_interface, + version, image_description_id); + if (!cm_image_desc->owner) { + free(cm_image_desc); + return NULL; + } + + wl_resource_set_implementation(cm_image_desc->owner, + &image_description_implementation, + cm_image_desc, + image_description_resource_destroy); + + cm_image_desc->cm = cm; + cm_image_desc->cprof = weston_color_profile_ref(cprof); + cm_image_desc->supports_get_info = supports_get_info; + + return cm_image_desc; +} + +/** + * Destroy an image description object. + */ +static void +cm_image_desc_destroy(struct cm_image_desc *cm_image_desc) +{ + weston_color_profile_unref(cm_image_desc->cprof); + free(cm_image_desc); +} + +/** + * Called by clients when they want to get the output's image description. + */ +static void +cm_output_get_image_description(struct wl_client *client, + struct wl_resource *cm_output_res, + uint32_t image_description_id) +{ + struct weston_head *head = wl_resource_get_user_data(cm_output_res); + struct weston_compositor *compositor; + struct weston_output *output; + uint32_t version = wl_resource_get_version(cm_output_res); + struct cm_image_desc *cm_image_desc; + struct wl_resource *cm_image_desc_res; + + /* The protocol states that if the wl_output global (which is backed by + * the weston_head object) no longer exists, we should immediately send + * a "failed" event for the image desc. After receiving that, clients + * are not allowed to make requests other than "destroy" for the image + * description. So let's avoid creating a cm_image_desc object, let's + * create only the resource and send the failed event. */ + if (!head) { + cm_image_desc_res = + wl_resource_create(client, &xx_image_description_v2_interface, + version, image_description_id); + if (!cm_image_desc_res) { + wl_resource_post_no_memory(cm_output_res); + return; + } + + wl_resource_set_implementation(cm_image_desc_res, + &image_description_implementation, + NULL, image_description_resource_destroy); + + xx_image_description_v2_send_failed(cm_image_desc_res, + XX_IMAGE_DESCRIPTION_V2_CAUSE_NO_OUTPUT, + "the wl_output global no longer exists"); + return; + } + + compositor = head->compositor; + output = head->output; + + /* If the head becomes inactive (head->output == NULL), the respective + * wl_output global gets destroyed. In such case we make the cm_output + * object inert. We do that in weston_head_remove_global(), and the + * cm_output_res user data (which was the head itself) is set to NULL. + * So if we reached here, head is active and head->output != NULL. */ + weston_assert_ptr(compositor, output); + + cm_image_desc = cm_image_desc_create(compositor->color_manager, + output->color_profile, client, + version, image_description_id, + YES_GET_INFO); + if (!cm_image_desc) { + wl_resource_post_no_memory(cm_output_res); + return; + } + + xx_image_description_v2_send_ready(cm_image_desc->owner, + cm_image_desc->cprof->id); +} + +/** + * Client will not use the cm_output anymore, so we destroy its resource. + */ +static void +cm_output_destroy(struct wl_client *client, struct wl_resource *cm_output_res) +{ + wl_resource_destroy(cm_output_res); +} + +/** + * Resource destruction function for the cm_output. + */ +static void +cm_output_resource_destroy(struct wl_resource *cm_output_res) +{ + struct weston_head *head = wl_resource_get_user_data(cm_output_res); + + /* For inert cm_output, we don't have to do anything. + * + * If the cm_get_output() was called after we made the head inactive, we + * created the cm_output with no resource user data and didn't add the + * resource link to weston_head::cm_output_resource_list. + * + * If the cm_output was created with an active head but it became + * inactive later, we have already done what is necessary when cm_output + * became inert, in weston_head_remove_global(). */ + if (!head) + return; + + /* We are destroying the cm_output_res, so simply remove it from + * weston_head::cm_output_resource_list. */ + wl_list_remove(wl_resource_get_link(cm_output_res)); +} + +static const struct xx_color_management_output_v2_interface +cm_output_implementation = { + .destroy = cm_output_destroy, + .get_image_description = cm_output_get_image_description, +}; + +/** + * This function is called by libweston when the struct weston_output color + * profile is updated. + * + * For each weston_head attached to the weston_output, we need to tell clients + * that the cm_output image description has changed. Also, for each surface + * whose primary output is the given, we need to send the preferred image + * description changed event. + * + * If this is called during output initialization, this function is no-op. There + * will be no client resources in weston_head::cm_output_resource_list and + * neither surfaces whose primary output is the one we are dealing with. + * + * \param output The weston_output that changed the color profile. + */ +void +weston_output_send_image_description_changed(struct weston_output *output) +{ + struct weston_head *head; + struct wl_resource *res; + int ver; + + /* For each head attached to this weston_output, send the events that + * notifies that the output image description changed. */ + wl_list_for_each(head, &output->head_list, output_link) { + wl_resource_for_each(res, &head->cm_output_resource_list) + xx_color_management_output_v2_send_image_description_changed(res); + + /* wl_output.done should be sent after collecting all the + * changes related to the output. But in Weston we are lacking + * an atomic output configuration API, so we have no facilities + * to do that. + * + * TODO: enhance this behavior after we add the atomic output + * configuration API. + */ + wl_resource_for_each(res, &head->resource_list) { + ver = wl_resource_get_version(res); + if (ver >= WL_OUTPUT_DONE_SINCE_VERSION) + wl_output_send_done(res); + } + } +} + +/** + * Client called get_output(). We already have the backing object, so just + * create a resource for the client. + */ +static void +cm_get_output(struct wl_client *client, struct wl_resource *cm_res, + uint32_t cm_output_id, struct wl_resource *output_res) +{ + struct weston_head *head = weston_head_from_resource(output_res); + uint32_t version = wl_resource_get_version(cm_res); + struct wl_resource *res; + + res = wl_resource_create(client, &xx_color_management_output_v2_interface, + version, cm_output_id); + if (!res) { + wl_resource_post_no_memory(cm_res); + return; + } + + /* Client wants the cm_output but we've already made the head inactive, + * so let's set the implementation data as NULL. */ + if (!head) { + wl_resource_set_implementation(res, &cm_output_implementation, + NULL, cm_output_resource_destroy); + return; + } + + wl_resource_set_implementation(res, &cm_output_implementation, + head, cm_output_resource_destroy); + + wl_list_insert(&head->cm_output_resource_list, + wl_resource_get_link(res)); +} + +/** + * Called by clients to update the image description of a surface. + * + * If the surface state is commited, libweston will update the struct + * weston_surface color profile and render intent. + */ +static void +cm_surface_set_image_description(struct wl_client *client, + struct wl_resource *cm_surface_res, + struct wl_resource *cm_image_desc_res, + uint32_t protocol_render_intent) +{ + struct weston_surface *surface = wl_resource_get_user_data(cm_surface_res); + struct cm_image_desc *cm_image_desc = + wl_resource_get_user_data(cm_image_desc_res); + struct weston_color_manager *cm = cm_image_desc->cm; + const struct weston_render_intent_info *render_intent; + + /* The surface might have been already gone, in such case cm_surface is + * inert. */ + if (!surface) { + wl_resource_post_error(cm_surface_res, + XX_COLOR_MANAGEMENT_SURFACE_V2_ERROR_INERT, + "the wl_surface has already been destroyed"); + return; + } + + render_intent = weston_render_intent_info_from_protocol(surface->compositor, + protocol_render_intent); + if (!render_intent) { + wl_resource_post_error(cm_surface_res, + XX_COLOR_MANAGEMENT_SURFACE_V2_ERROR_RENDER_INTENT, + "unknown render intent"); + return; + } + + if (!((cm->supported_rendering_intents >> render_intent->intent) & 1)) { + wl_resource_post_error(cm_surface_res, + XX_COLOR_MANAGEMENT_SURFACE_V2_ERROR_RENDER_INTENT, + "unsupported render intent"); + return; + } + + weston_color_profile_unref(surface->pending.color_profile); + surface->pending.color_profile = + weston_color_profile_ref(cm_image_desc->cprof); + surface->pending.render_intent = render_intent; +} + +/** + * Called by clients to unset the image description. + * + * If the surface state is commited, libweston will update the struct + * weston_surface color profile and render intent. + */ +static void +cm_surface_unset_image_description(struct wl_client *client, + struct wl_resource *cm_surface_res) +{ + struct weston_surface *surface = wl_resource_get_user_data(cm_surface_res); + + /* The surface might have been already gone, in such case cm_surface is + * inert. */ + if (!surface) { + wl_resource_post_error(cm_surface_res, + XX_COLOR_MANAGEMENT_SURFACE_V2_ERROR_INERT, + "the wl_surface has already been destroyed"); + return; + } + + weston_color_profile_unref(surface->pending.color_profile); + surface->pending.color_profile = NULL; + surface->pending.render_intent = NULL; +} + +/** + * Called by clients when they want to know the preferred image description of + * the surface. + */ +static void +cm_surface_get_preferred(struct wl_client *client, struct wl_resource *cm_surface_res, + uint32_t image_description_id) +{ + struct weston_surface *surface = wl_resource_get_user_data(cm_surface_res); + uint32_t version = wl_resource_get_version(cm_surface_res); + struct weston_color_manager *cm; + struct cm_image_desc *cm_image_desc; + + /* The surface might have been already gone, in such case cm_surface is + * inert. */ + if (!surface) { + wl_resource_post_error(cm_surface_res, + XX_COLOR_MANAGEMENT_SURFACE_V2_ERROR_INERT, + "the wl_surface has already been destroyed"); + return; + } + + cm = surface->compositor->color_manager; + + cm_image_desc = cm_image_desc_create(cm, surface->preferred_color_profile, + client, version, image_description_id, + YES_GET_INFO); + if (!cm_image_desc) { + wl_resource_post_no_memory(cm_surface_res); + return; + } + + xx_image_description_v2_send_ready(cm_image_desc->owner, + cm_image_desc->cprof->id); +} + +/** + * Client will not use the cm_surface anymore, so we destroy its resource. + */ +static void +cm_surface_destroy(struct wl_client *client, struct wl_resource *cm_surface_res) +{ + wl_resource_destroy(cm_surface_res); +} + +/** + * Resource destruction function for the cm_surface. + */ +static void +cm_surface_resource_destroy(struct wl_resource *cm_surface_res) +{ + struct weston_surface *surface = wl_resource_get_user_data(cm_surface_res); + + /* For inert cm_surface, we don't have to do anything. + * + * We already did what was necessary when cm_surface became inert, in + * the surface destruction process (in weston_surface_unref(), which + * is the surface destruction function). */ + if (!surface) + return; + + /* We are destroying the cm_surface_res, so simply remove it from + * weston_surface::cm_surface_resource_list. */ + wl_list_remove(wl_resource_get_link(cm_surface_res)); + + /* TODO: if wl_list_empty(&surface->cm_surface_resource_list), we need + * to unset_image_description for the surface. The protocol states that + * we need to do that after the last cm_surface object for a wl_surface + * is destroyed. */ +} + +static const struct xx_color_management_surface_v2_interface +cm_surface_implementation = { + .destroy = cm_surface_destroy, + .set_image_description = cm_surface_set_image_description, + .unset_image_description = cm_surface_unset_image_description, + .get_preferred = cm_surface_get_preferred, +}; + +/** + * Notifies clients that their surface preferred image description changed. + * + * \param surface The surface that changed its preferred image description. + */ +void +weston_surface_send_preferred_image_description_changed(struct weston_surface *surface) +{ + struct wl_resource *res; + + /* For each resource, send the event that notifies that the surface + * preferred image description changed. */ + wl_resource_for_each(res, &surface->cm_surface_resource_list) + xx_color_management_surface_v2_send_preferred_changed(res); +} + +/** + * Client called get_surface(). We already have the backing object, so just + * create a resource for the client. + */ +static void +cm_get_surface(struct wl_client *client, struct wl_resource *cm_res, + uint32_t cm_surface_id, struct wl_resource *surface_res) +{ + struct weston_surface *surface = wl_resource_get_user_data(surface_res); + uint32_t version = wl_resource_get_version(cm_res); + struct wl_resource *res; + + res = wl_resource_create(client, &xx_color_management_surface_v2_interface, + version, cm_surface_id); + if (!res) { + wl_resource_post_no_memory(cm_res); + return; + } + + wl_resource_set_implementation(res, &cm_surface_implementation, + surface, cm_surface_resource_destroy); + wl_list_insert(&surface->cm_surface_resource_list, wl_resource_get_link(res)); +} + +/** + * Sets the ICC file for the ICC-based image description creator object. + */ +static void +cm_creator_icc_set_icc_file(struct wl_client *client, + struct wl_resource *resource, + int32_t icc_profile_fd, + uint32_t offset, uint32_t length) +{ + struct cm_creator_icc *cm_creator_icc = wl_resource_get_user_data(resource); + int flags; + uint32_t err_code; + const char *err_msg; + + if (cm_creator_icc->icc_data_length > 0) { + err_code = XX_IMAGE_DESCRIPTION_CREATOR_ICC_V2_ERROR_ALREADY_SET; + err_msg = "ICC file was already set"; + goto err; + } + + /* Length should be in the (0, 4MB] interval */ + if (length == 0 || length > (4 * 1024 * 1024)) { + err_code = XX_IMAGE_DESCRIPTION_CREATOR_ICC_V2_ERROR_BAD_SIZE; + err_msg = "invalid ICC file size"; + goto err; + } + + /* Fd should be readable. */ + flags = fcntl(icc_profile_fd, F_GETFL); + if ((flags & O_ACCMODE) == O_WRONLY) { + err_code = XX_IMAGE_DESCRIPTION_CREATOR_ICC_V2_ERROR_BAD_FD; + err_msg = "ICC fd is not readable"; + goto err; + } + + /* Fd should be seekable. */ + if (lseek(icc_profile_fd, 0, SEEK_CUR) < 0) { + err_code = XX_IMAGE_DESCRIPTION_CREATOR_ICC_V2_ERROR_BAD_FD; + err_msg = "ICC fd is not seekable"; + goto err; + } + + cm_creator_icc->icc_profile_fd = icc_profile_fd; + + /* We save length and offset in size_t variables. This ensures that they + * fit. We received them as uint32_t from the protocol. */ + static_assert(UINT32_MAX <= SIZE_MAX, + "won't be able to save uint32_t var into size_t"); + cm_creator_icc->icc_data_length = length; + cm_creator_icc->icc_data_offset = offset; + + return; + +err: + close(icc_profile_fd); + wl_resource_post_error(resource, err_code, "%s", err_msg); +} + +static bool +do_length_and_offset_fit(struct cm_creator_icc *cm_creator_icc) +{ + size_t end; + off_t end_off; + + /* Ensure that length + offset doesn't overflow in size_t. If that isn't + * true, we won't be able to make it fit into off_t. And we may need + * that to read the ICC file. */ + if (cm_creator_icc->icc_data_length > SIZE_MAX - cm_creator_icc->icc_data_offset) + return false; + + /* Ensure that length + offset doesn't overflow in off_t. */ + end = cm_creator_icc->icc_data_offset + cm_creator_icc->icc_data_length; + end_off = end; + if (end_off < 0 || end != (size_t)end_off) + return false; + + return true; +} + +static void +create_image_description_color_profile_from_icc_creator(struct cm_image_desc *cm_image_desc, + struct cm_creator_icc *cm_creator_icc) +{ + struct weston_compositor *compositor = cm_creator_icc->compositor; + struct weston_color_manager *cm = compositor->color_manager; + struct weston_color_profile *cprof; + char *err_msg; + void *icc_prof_data; + size_t bytes_read = 0; + ssize_t pread_ret; + bool ret; + + if (!do_length_and_offset_fit(cm_creator_icc)) { + xx_image_description_v2_send_failed(cm_image_desc->owner, + XX_IMAGE_DESCRIPTION_V2_CAUSE_OPERATING_SYSTEM, + "length + offset does not fit off_t"); + return; + } + + /* Create buffer to read ICC profile. As they may have up to 4Mb, we + * send OOM if something fails (instead of using xalloc). */ + icc_prof_data = zalloc(cm_creator_icc->icc_data_length); + if (!icc_prof_data) { + wl_resource_post_no_memory(cm_creator_icc->owner); + return; + } + + /* Read ICC file. + * + * TODO: it is not that simple. Clients can abuse that to DoS the + * compositor. See the discussion in the link below. + * + * https://gitlab.freedesktop.org/wayland/weston/-/merge_requests/1356#note_2125102 + */ + while (bytes_read < cm_creator_icc->icc_data_length) { + pread_ret = pread(cm_creator_icc->icc_profile_fd, + icc_prof_data + bytes_read, + cm_creator_icc->icc_data_length - bytes_read, + (off_t)cm_creator_icc->icc_data_offset + bytes_read); + if (pread_ret < 0) { + /* Failed to read but not an error (just interruption), + * so continue trying to read. */ + if (errno == EINTR) + continue; + + /* Reading the ICC failed */ + free(icc_prof_data); + str_printf(&err_msg, "failed to read ICC file: %s", strerror(errno)); + xx_image_description_v2_send_failed(cm_image_desc->owner, + XX_IMAGE_DESCRIPTION_V2_CAUSE_OPERATING_SYSTEM, + err_msg); + free(err_msg); + return; + } else if (pread_ret == 0) { + /* We were expecting to read more than 0 bytes, but we + * didn't. That means that we've tried to read beyond + * EOF. This is client's fault, it must make sure that + * the given ICC file don't simply change. */ + free(icc_prof_data); + wl_resource_post_error(cm_creator_icc->owner, + XX_IMAGE_DESCRIPTION_CREATOR_ICC_V2_ERROR_OUT_OF_FILE, + "tried to read ICC beyond EOF"); + return; + } + bytes_read += (size_t)pread_ret; + } + weston_assert_true(compositor, bytes_read == cm_creator_icc->icc_data_length); + + /* We've read the ICC file so let's create the color profile. */ + ret = cm->get_color_profile_from_icc(cm, icc_prof_data, + cm_creator_icc->icc_data_length, + "icc-from-client", &cprof, &err_msg); + free(icc_prof_data); + + if (!ret) { + /* We can't tell if it is client's fault that the ICC profile is + * invalid, so let's gracefully fail without returning a + * protocol error. + * + * TODO: we need to return proper error codes from the + * color-manager plugins and decide if we should gracefully fail + * or return a protocol error. + */ + xx_image_description_v2_send_failed(cm_image_desc->owner, + XX_IMAGE_DESCRIPTION_V2_CAUSE_UNSUPPORTED, + err_msg); + free(err_msg); + return; + } + + cm_image_desc->cprof = cprof; + xx_image_description_v2_send_ready(cm_image_desc->owner, + cm_image_desc->cprof->id); +} + +/** + * Creates image description using the ICC-based image description creator + * object. This is a destructor type request, so the cm_creator_icc resource + * gets destroyed after this. + */ +static void +cm_creator_icc_create(struct wl_client *client, struct wl_resource *resource, + uint32_t image_description_id) +{ + struct cm_creator_icc *cm_creator_icc = + wl_resource_get_user_data(resource); + struct weston_compositor *compositor = cm_creator_icc->compositor; + struct weston_color_manager *cm = compositor->color_manager; + uint32_t version = wl_resource_get_version(cm_creator_icc->owner); + struct cm_image_desc *cm_image_desc; + + if (cm_creator_icc->icc_data_length == 0) { + wl_resource_post_error(resource, + XX_IMAGE_DESCRIPTION_CREATOR_ICC_V2_ERROR_INCOMPLETE_SET, + "trying to create image description before " \ + "setting the ICC file"); + return; + } + + /* Create the image description with cprof == NULL. */ + cm_image_desc = cm_image_desc_create(cm, NULL, client, version, + image_description_id, NO_GET_INFO); + if (!cm_image_desc) { + wl_resource_post_no_memory(resource); + return; + } + + /* Create the cprof for the image description. */ + create_image_description_color_profile_from_icc_creator(cm_image_desc, + cm_creator_icc); + + /* Destroy the cm_creator_icc resource. This is a destructor request. */ + wl_resource_destroy(cm_creator_icc->owner); +} + +/** + * Resource destruction function for the cm_creator_icc. + * It should only destroy itself, but not the image description it creates. + */ +static void +cm_creator_icc_destructor(struct wl_resource *resource) +{ + struct cm_creator_icc *cm_creator_icc = + wl_resource_get_user_data(resource); + + if (cm_creator_icc->icc_profile_fd >= 0) + close(cm_creator_icc->icc_profile_fd); + + free(cm_creator_icc); +} + +static const struct xx_image_description_creator_icc_v2_interface +cm_creator_icc_implementation = { + .create = cm_creator_icc_create, + .set_icc_file = cm_creator_icc_set_icc_file, +}; + +/** + * Creates an ICC-based image description creator for the client. + */ +static void +cm_new_image_description_creator_icc(struct wl_client *client, struct wl_resource *cm_res, + uint32_t cm_creator_icc_id) +{ + struct cm_creator_icc *cm_creator_icc; + struct weston_compositor *compositor = wl_resource_get_user_data(cm_res); + struct weston_color_manager *cm = compositor->color_manager; + uint32_t version = wl_resource_get_version(cm_res); + + if (!((cm->supported_color_features >> WESTON_COLOR_FEATURE_ICC) & 1)) { + wl_resource_post_error(cm_res, XX_COLOR_MANAGER_V2_ERROR_UNSUPPORTED_FEATURE, + "creating ICC image description creator is " \ + "still unsupported"); + return; + } + + cm_creator_icc = xzalloc(sizeof(*cm_creator_icc)); + + cm_creator_icc->compositor = compositor; + cm_creator_icc->icc_profile_fd = -1; + + cm_creator_icc->owner = + wl_resource_create(client, &xx_image_description_creator_icc_v2_interface, + version, cm_creator_icc_id); + if (!cm_creator_icc->owner) + goto err; + + wl_resource_set_implementation(cm_creator_icc->owner, &cm_creator_icc_implementation, + cm_creator_icc, cm_creator_icc_destructor); + + return; + +err: + free(cm_creator_icc); + wl_resource_post_no_memory(cm_res); +} + +/** + * Creates a parametric image description creator for the client. + */ +static void +cm_new_image_description_creator_params(struct wl_client *client, struct wl_resource *cm_res, + uint32_t cm_creator_params_id) +{ + /* Still unsupported. */ + wl_resource_post_error(cm_res, XX_COLOR_MANAGER_V2_ERROR_UNSUPPORTED_FEATURE, + "creating parametric image description creator is " \ + "still unsupported"); +} + +/** + * Client will not use the color management object anymore, so we destroy its + * resource. That should not affect the other objects in any way. + */ +static void +cm_destroy(struct wl_client *client, struct wl_resource *cm_res) +{ + wl_resource_destroy(cm_res); +} + +static const struct xx_color_manager_v2_interface +color_manager_implementation = { + .destroy = cm_destroy, + .get_output = cm_get_output, + .get_surface = cm_get_surface, + .new_icc_creator = cm_new_image_description_creator_icc, + .new_parametric_creator = cm_new_image_description_creator_params, +}; + +/** + * Called when clients bind to the color-management protocol. + */ +static void +bind_color_management(struct wl_client *client, void *data, uint32_t version, + uint32_t id) +{ + struct wl_resource *resource; + struct weston_compositor *compositor = data; + struct weston_color_manager *cm = compositor->color_manager; + const struct weston_color_feature_info *feature_info; + const struct weston_render_intent_info *render_intent; + unsigned int i; + + resource = wl_resource_create(client, &xx_color_manager_v2_interface, + version, id); + if (!resource) { + wl_client_post_no_memory(client); + return; + } + + wl_resource_set_implementation(resource, &color_manager_implementation, + compositor, NULL); + + /* Expose the supported color features to the client. */ + for (i = 0; i < 32; i++) { + if (!((cm->supported_color_features >> i) & 1)) + continue; + feature_info = weston_color_feature_info_from(compositor, i); + xx_color_manager_v2_send_supported_feature(resource, + feature_info->protocol_feature); + } + + /* Expose the supported rendering intents to the client. */ + for (i = 0; i < 32; i++) { + if (!((cm->supported_rendering_intents >> i) & 1)) + continue; + render_intent = weston_render_intent_info_from(compositor, i); + xx_color_manager_v2_send_supported_intent(resource, + render_intent->protocol_intent); + } +} + +/** Advertise color-management support + * + * Calling this initializes the color-management protocol support, so that + * xx_color_manager_v2_interface will be advertised to clients. Essentially it + * creates a global. Do not call this function multiple times in the + * compositor's lifetime. There is no way to deinit explicitly, globals will be + * reaped when the wl_display gets destroyed. + * + * \param compositor The compositor to init for. + * \return Zero on success, -1 on failure. + */ +WL_EXPORT int +weston_compositor_enable_color_management_protocol(struct weston_compositor *compositor) +{ + uint32_t version = 1; + + if (!wl_global_create(compositor->wl_display, + &xx_color_manager_v2_interface, + version, compositor, bind_color_management)) + return -1; + + return 0; +} diff --git a/libweston/color-management.h b/libweston/color-management.h new file mode 100644 index 000000000..9fcdbe26e --- /dev/null +++ b/libweston/color-management.h @@ -0,0 +1,56 @@ +/* + * Copyright 2023 Collabora, Ltd. + * + * 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. + */ + +#ifndef WESTON_COLOR_MANAGEMENT_H +#define WESTON_COLOR_MANAGEMENT_H + +#include "color-properties.h" + +int +weston_compositor_enable_color_management_protocol(struct weston_compositor *compositor); + +void +weston_output_send_image_description_changed(struct weston_output *output); + +void +weston_surface_send_preferred_image_description_changed(struct weston_surface *surface); + +void +weston_cm_send_icc_file(struct cm_image_desc_info *cm_image_desc_info, + int32_t fd, uint32_t len); + +void +weston_cm_send_primaries_named(struct cm_image_desc_info *cm_image_desc_info, + const struct weston_color_primaries_info *primaries_info); + +void +weston_cm_send_primaries(struct cm_image_desc_info *cm_image_desc_info, + const struct weston_color_gamut *color_gamut); + +void +weston_cm_send_tf_named(struct cm_image_desc_info *cm_image_desc_info, + const struct weston_color_tf_info *tf_info); + +#endif /* WESTON_COLOR_MANAGEMENT_H */ diff --git a/libweston/color-noop.c b/libweston/color-noop.c index 182af7509..c784eab98 100644 --- a/libweston/color-noop.c +++ b/libweston/color-noop.c @@ -30,6 +30,7 @@ #include "color.h" #include "shared/helpers.h" #include "shared/xalloc.h" +#include "shared/weston-assert.h" struct cmnoop_color_profile { struct weston_color_profile base; @@ -146,12 +147,18 @@ cmnoop_get_surface_color_transform(struct weston_color_manager *cm_base, struct weston_output *output, struct weston_surface_color_transform *surf_xform) { + struct weston_compositor *compositor = output->compositor; struct weston_color_manager_noop *cmnoop = get_cmnoop(cm_base); - /* TODO: Assert that, if the surface has a cprof, it is the stock one */ + /* If surface has a cprof, it has to be the stock one. */ + if (surface->color_profile) + weston_assert_ptr_eq(compositor, get_cprof(surface->color_profile), + cmnoop->stock_cprof); - assert(output->color_profile && - get_cprof(output->color_profile) == cmnoop->stock_cprof); + /* The output must have a cprof, and it has to be the stock one. */ + weston_assert_ptr(compositor, output->color_profile); + weston_assert_ptr_eq(compositor, get_cprof(output->color_profile), + cmnoop->stock_cprof); if (!check_output_eotf_mode(output)) return false; @@ -242,9 +249,14 @@ weston_color_manager_noop_create(struct weston_compositor *compositor) cm->base.destroy_color_profile = cmnoop_destroy_color_profile; cm->base.get_stock_sRGB_color_profile = cmnoop_get_stock_sRGB_color_profile; cm->base.get_color_profile_from_icc = cmnoop_get_color_profile_from_icc; + cm->base.send_image_desc_info = NULL; cm->base.destroy_color_transform = cmnoop_destroy_color_transform; cm->base.get_surface_color_transform = cmnoop_get_surface_color_transform; cm->base.create_output_color_outcome = cmnoop_create_output_color_outcome; + /* We don't support anything related to the CM&HDR protocol extension */ + cm->base.supported_color_features = 0; + cm->base.supported_rendering_intents = 0; + return &cm->base; } diff --git a/libweston/color.h b/libweston/color.h index 2cc8f11ba..aeb61f9d4 100644 --- a/libweston/color.h +++ b/libweston/color.h @@ -243,6 +243,8 @@ struct weston_surface_color_transform { bool identity_pipeline; }; +struct cm_image_desc_info; + struct weston_color_manager { /** Identifies this CMS component */ const char *name; @@ -253,6 +255,22 @@ struct weston_color_manager { /** Supports the Wayland CM&HDR protocol extension? */ bool supports_client_protocol; + /** + * Supported color features from Wayland CM&HDR protocol extension. + * + * If v (v being enum weston_color_feature v) is a supported color + * feature, the bit v of this will be set to 1. + */ + uint32_t supported_color_features; + + /** + * Supported rendering intents from Wayland CM&HDR protocol extension. + * + * If v (v being enum weston_render_intent v) is a supported rendering + * intent, the bit v of this will be set to 1. + */ + uint32_t supported_rendering_intents; + /** Initialize color manager */ bool (*init)(struct weston_color_manager *cm); @@ -298,6 +316,23 @@ struct weston_color_manager { struct weston_color_profile **cprof_out, char **errmsg); + /** Send image description to clients. + * + * \param cm_image_desc_info The image description info object + * \param cprof_base The color profile that backs the image description + * \return True on success, false on failure + * + * This should be used only by the CM&HDR protocol extension + * implementation. + * + * The color manager implementing this function should use the helpers + * from color-management.c (weston_cm_send_primaries(), etc) to send the + * information to clients. + */ + bool + (*send_image_desc_info)(struct cm_image_desc_info *cm_image_desc_info, + struct weston_color_profile *cprof_base); + /** Destroy a color transform after refcount fell to zero */ void (*destroy_color_transform)(struct weston_color_transform *xform); diff --git a/libweston/compositor.c b/libweston/compositor.c index 166d6a12d..8d8aa778a 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -72,6 +72,7 @@ #include "shared/string-helpers.h" #include "shared/timespec-util.h" #include "shared/xalloc.h" +#include "shared/weston-assert.h" #include "tearing-control-v1-server-protocol.h" #include "git-version.h" #include @@ -80,6 +81,7 @@ #include "backend.h" #include "libweston-internal.h" #include "color.h" +#include "color-management.h" #include "id-number-allocator.h" #include "output-capture.h" #include "pixman-renderer.h" @@ -779,6 +781,9 @@ weston_surface_state_init(struct weston_surface *surface, state->desired_protection = WESTON_HDCP_DISABLE; state->protection_mode = WESTON_SURFACE_PROTECTION_MODE_RELAXED; + + state->color_profile = NULL; + state->render_intent = NULL; } static void @@ -802,6 +807,10 @@ weston_surface_state_fini(struct weston_surface_state *state) fd_clear(&state->acquire_fence_fd); weston_buffer_release_reference(&state->buffer_release_ref, NULL); + + weston_color_profile_unref(state->color_profile); + state->color_profile = NULL; + state->render_intent = NULL; } static void @@ -819,6 +828,55 @@ weston_surface_state_set_buffer(struct weston_surface_state *state, &state->buffer_destroy_listener); } +static void +weston_surface_update_preferred_color_profile(struct weston_surface *surface) +{ + struct weston_compositor *compositor = surface->compositor; + struct weston_color_manager *cm = compositor->color_manager; + struct weston_color_profile *old, *new; + + old = surface->preferred_color_profile; + + if (surface->output) { + /* The surface preferred color profile is the same color profile + * of its primary output. */ + new = weston_color_profile_ref(surface->output->color_profile); + } else if (!wl_list_empty(&compositor->output_list)) { + /* Surface is still unmapped, with no primary output. To map the + * surface, clients need to draw, and in order to do that they + * should ask the preferred color profile for the surface (at + * least for color-aware clients). So in order to maximize the + * changes of the first frame being correct, we arbitrarily pick + * an output and use its color profile as the preferred. The + * most common scenario is a system with a single monitor (and + * output), so when the surface gets mapped this output will + * become the surface primary one, and the preferred color + * profile will stay the same. */ + struct weston_output *output; + output = wl_container_of(surface->compositor->output_list.next, + output, link); + new = weston_color_profile_ref(output->color_profile); + } else { + /* Unmapped surface and no outputs available, so let's pick + * stock sRGB color profile. */ + new = cm->get_stock_sRGB_color_profile(cm); + } + + /* Nothing to do. */ + if (new == old) { + weston_color_profile_unref(new); + return; + } + + weston_color_profile_unref(old); + + /* Update the preferred color profile and notify color-aware clients + * that the surface preferred image description changed. Part of the + * CM&HDR protocol extension implementation. */ + surface->preferred_color_profile = new; + weston_surface_send_preferred_image_description_changed(surface); +} + WL_EXPORT struct weston_surface * weston_surface_create(struct weston_compositor *compositor) { @@ -867,6 +925,17 @@ weston_surface_create(struct weston_compositor *compositor) surface->current_protection = WESTON_HDCP_DISABLE; surface->protection_mode = WESTON_SURFACE_PROTECTION_MODE_RELAXED; + wl_list_init(&surface->cm_surface_resource_list); + + /* The surfaces start with no color profile and render intent. It's up + * to the color manager what to do with that. Later, clients are able to + * define these values using the CM&HDR protocol extension. */ + surface->color_profile = NULL; + surface->render_intent = NULL; + + /* Also part of the CM&HDR protocol extension implementation. */ + weston_surface_update_preferred_color_profile(surface); + return surface; } @@ -1129,6 +1198,46 @@ weston_surface_send_enter_leave(struct weston_surface *surface, } } +/** Set the color profile and render intent of a surface. + * + * \param surface The surface to update + * \param cprof The new color profile, or NULL + * \param render_intent The render intent info object, or NULL + * + * It is forbidden to pass a valid cprof and a NULL render intent, and + * vice-versa. But both NULL is valid. + */ +void +weston_surface_set_color_profile(struct weston_surface *surface, + struct weston_color_profile *cprof, + const struct weston_render_intent_info *render_intent) +{ + struct weston_color_manager *cm = surface->compositor->color_manager; + struct weston_paint_node *pnode; + + /* Nothing to do. */ + if (surface->color_profile == cprof && + surface->render_intent == render_intent) + return; + + if (!!cprof ^ !!render_intent) + weston_assert_not_reached(cm->compositor, + "received valid cprof and NULL render intent, " \ + "or vice versa; invalid for this function"); + + /* Remove outdated cached color transformations */ + wl_list_for_each(pnode, &surface->paint_node_list, surface_link) { + weston_surface_color_transform_fini(&pnode->surf_xform); + pnode->surf_xform_valid = false; + } + + /* Caller gave us a color profile and render intent (or NULL for both, + * which is also valid), so update the surface with them. */ + weston_color_profile_unref(surface->color_profile); + surface->color_profile = weston_color_profile_ref(cprof); + surface->render_intent = render_intent; +} + static void weston_surface_compute_protection(struct protected_surface *psurface) { @@ -1378,6 +1487,11 @@ weston_surface_assign_output(struct weston_surface *es) es->output = new_output; weston_surface_update_output_mask(es, mask); + + /* Surface primary output may have changed, and that may change the + * surface preferred color profile. Part of the CM&HDR protocol + * extension implementation. */ + weston_surface_update_preferred_color_profile(es); } /** Recalculate which output(s) the view is displayed on @@ -2487,6 +2601,7 @@ weston_surface_unref(struct weston_surface *surface) struct wl_resource *cb, *next; struct weston_view *ev, *nv; struct weston_pointer_constraint *constraint, *next_constraint; + struct wl_resource *cm_surface_res, *cm_surface_res_tmp; struct weston_paint_node *pnode, *pntmp; if (!surface) @@ -2539,6 +2654,16 @@ weston_surface_unref(struct weston_surface *surface) if (surface->tear_control) surface->tear_control->surface = NULL; + weston_color_profile_unref(surface->color_profile); + weston_color_profile_unref(surface->preferred_color_profile); + + wl_resource_for_each_safe(cm_surface_res, cm_surface_res_tmp, + &surface->cm_surface_resource_list) { + wl_list_remove(wl_resource_get_link(cm_surface_res)); + wl_list_init(wl_resource_get_link(cm_surface_res)); + wl_resource_set_user_data(cm_surface_res, NULL); + } + free(surface); } @@ -4549,6 +4674,11 @@ weston_surface_commit_state(struct weston_surface *surface, /* weston_protected_surface.set_type */ weston_surface_set_desired_protection(surface, state->desired_protection); + /* color_management_surface_v1_interface.set_image_description or + * color_management_surface_v1_interface.unset_image_description */ + weston_surface_set_color_profile(surface, state->color_profile, + state->render_intent); + wl_signal_emit(&surface->commit_signal, surface); /* Surface is now quiescent */ @@ -4878,6 +5008,10 @@ weston_subsurface_commit_to_cache(struct weston_subsurface *sub) &surface->pending.damage_buffer); pixman_region32_clear(&surface->pending.damage_buffer); + sub->cached.render_intent = surface->pending.render_intent; + weston_color_profile_unref(sub->cached.color_profile); + sub->cached.color_profile = + weston_color_profile_ref(surface->pending.color_profile); if (surface->pending.status & WESTON_SURFACE_DIRTY_BUFFER) { weston_surface_state_set_buffer(&sub->cached, @@ -6014,6 +6148,12 @@ weston_head_remove_global(struct weston_head *head) wl_resource_set_destructor(resource, NULL); } wl_list_init(&head->xdg_output_resource_list); + + wl_resource_for_each_safe(resource, tmp, &head->cm_output_resource_list) { + wl_list_remove(wl_resource_get_link(resource)); + wl_list_init(wl_resource_get_link(resource)); + wl_resource_set_user_data(resource, NULL); + } } /** Get the backing object of wl_output @@ -6059,6 +6199,7 @@ weston_head_init(struct weston_head *head, const char *name) wl_list_init(&head->output_link); wl_list_init(&head->resource_list); wl_list_init(&head->xdg_output_resource_list); + wl_list_init(&head->cm_output_resource_list); head->name = xstrdup(name); head->supported_eotf_mask = WESTON_EOTF_MODE_SDR; head->current_protection = WESTON_HDCP_DISABLE; @@ -7430,18 +7571,24 @@ WL_EXPORT bool weston_output_set_color_profile(struct weston_output *output, struct weston_color_profile *cprof) { - struct weston_color_manager *cm = output->compositor->color_manager; - struct weston_color_profile *old; + struct weston_compositor *compositor = output->compositor; + struct weston_color_manager *cm = compositor->color_manager; + struct weston_color_profile *old, *new; struct weston_paint_node *pnode; + struct weston_view *view; old = output->color_profile; + new = cprof ? weston_color_profile_ref(cprof) : + cm->get_stock_sRGB_color_profile(cm); - if (!cprof) { - output->color_profile = cm->get_stock_sRGB_color_profile(cm); - } else { - output->color_profile = weston_color_profile_ref(cprof); + /* Nothing to do. */ + if (new == old) { + weston_color_profile_unref(new); + return true; } + output->color_profile = new; + if (output->enabled) { if (!weston_output_set_color_outcome(output)) { /* Failed, roll back */ @@ -7455,10 +7602,22 @@ weston_output_set_color_profile(struct weston_output *output, weston_surface_color_transform_fini(&pnode->surf_xform); pnode->surf_xform_valid = false; } + + /* The preferred color profile of a surface is its primary + * output color profile. For each surface that has this output + * as primary, we may need to update their preferred color + * profile. Part of the CM&HDR protocol extension + * implementation. */ + wl_list_for_each(view, &compositor->view_list, link) + weston_surface_update_preferred_color_profile(view->surface); } weston_color_profile_unref(old); + /* Output color profile has changed, so we need to notify clients about + * that. Part of the CM&HDR protocol extension implementation. */ + weston_output_send_image_description_changed(output); + return true; } diff --git a/libweston/meson.build b/libweston/meson.build index b2528c1c4..506e88353 100644 --- a/libweston/meson.build +++ b/libweston/meson.build @@ -15,6 +15,7 @@ srcs_libweston = [ 'clipboard.c', 'color.c', 'color-properties.c', + 'color-management.c', 'color-noop.c', 'compositor.c', 'content-protection.c', @@ -39,6 +40,8 @@ srcs_libweston = [ 'weston-log-flight-rec.c', 'weston-log.c', 'weston-direct-display.c', + color_management_v1_protocol_c, + color_management_v1_server_protocol_h, linux_dmabuf_unstable_v1_protocol_c, linux_dmabuf_unstable_v1_server_protocol_h, linux_explicit_synchronization_unstable_v1_protocol_c, diff --git a/shared/weston-assert.h b/shared/weston-assert.h index b0771e1d0..98c358c42 100644 --- a/shared/weston-assert.h +++ b/shared/weston-assert.h @@ -99,6 +99,9 @@ do { \ #define weston_assert_uint32_neq(compositor, a, b) \ weston_assert_(compositor, a, b, uint32_t, "%u", !=) +#define weston_assert_uint32_gt(compositor, a, b) \ + weston_assert_(compositor, a, b, uint32_t, "%u", >) + #define weston_assert_uint32_lt(compositor, a, b) \ weston_assert_(compositor, a, b, uint32_t, "%u", <)