diff --git a/frontend/main.c b/frontend/main.c index 02c238485..730543748 100644 --- a/frontend/main.c +++ b/frontend/main.c @@ -4076,9 +4076,13 @@ load_drm_backend(struct weston_compositor *c, int *argc, char **argv, weston_config_section_get_bool(section, "offload-blend-to-output", &offload_blend_to_output, false); - if (!c->color_manager && offload_blend_to_output) + if (!c->color_manager) offload_blend_to_output = false; +#if !CAN_OFFLOAD_COLOR_PIPELINE + offload_blend_to_output = false; +#endif + config.offload_blend_to_output = offload_blend_to_output; weston_config_section_get_string(section, diff --git a/libweston/backend-drm/colorops.c b/libweston/backend-drm/colorops.c new file mode 100644 index 000000000..4da8a8b77 --- /dev/null +++ b/libweston/backend-drm/colorops.c @@ -0,0 +1,1322 @@ +/* + * Copyright © 2025-2026 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 + +#include "colorops.h" +#include "color-properties.h" +#include "drm-internal.h" +#include "shared/string-helpers.h" +#include "shared/weston-assert.h" +#include "shared/xalloc.h" + +static void +drm_colorop_3x1d_lut_blob_destroy(struct drm_colorop_3x1d_lut_blob *lut) +{ + wl_list_remove(&lut->destroy_listener.link); + wl_list_remove(&lut->link); + drmModeDestroyPropertyBlob(lut->device->kms_device->fd, lut->blob_id); + free(lut); +} + +static void +drm_colorop_3x1d_lut_blob_destroy_handler(struct wl_listener *l, void *data) +{ + struct drm_colorop_3x1d_lut_blob *lut; + + lut = wl_container_of(l, lut, destroy_listener); + assert(lut->xform == data); + + drm_colorop_3x1d_lut_blob_destroy(lut); +} + +/** + * Search for a 3x1D LUT colorop blob in a DRM device. + * + * \param device The DRM device in which we want to look for the blob. + * \param xform The xform from which the LUT comes from. + * \param curve_step What curve step from the xform originated the 3x1D LUT. + * \param quantization The colorop 3x1D LUT quantization (U32 or U16). + * \param lut_len How many taps each of the 1D LUT has. + */ +struct drm_colorop_3x1d_lut_blob * +drm_colorop_3x1d_lut_blob_search(struct drm_device *device, + struct weston_color_transform *xform, + enum weston_color_curve_step curve_step, + enum drm_colorop_3x1d_lut_blob_quantization quantization, + uint32_t lut_len) +{ + struct drm_colorop_3x1d_lut_blob *lut; + + wl_list_for_each(lut, &device->drm_colorop_3x1d_lut_blob_list, link) + if (lut->xform == xform && lut->curve_step == curve_step && + lut->lut_len == lut_len && lut->quantization == quantization) + return lut; + + return NULL; +} + +static struct drm_color_lut32 +drm_vec3f_to_u32(struct weston_vec3f vec) +{ + struct drm_color_lut32 res; + + /* UINT32_MAX exceeds the 24-bit integer precision of floats and could + * be rounded incorrectly if multiplied in float. */ + + res.red = (double) vec.r * UINT32_MAX; + res.green = (double) vec.g * UINT32_MAX; + res.blue = (double) vec.b * UINT32_MAX; + + return res; +} + +static struct drm_color_lut +drm_vec3f_to_u16(struct weston_vec3f vec) +{ + struct drm_color_lut res; + + res.red = vec.r * UINT16_MAX; + res.green = vec.g * UINT16_MAX; + res.blue = vec.b * UINT16_MAX; + + return res; +} + +/** + * Create a 3x1D LUT colorop blob. + * + * A Weston colorop is an object associated with a step from a struct + * weston_color_transform (or the xform itself, when it gets decomposed to + * shaper + 3D LUT) and that can be used to program KMS color operations. This + * function creates a blob for such kind of object and cache that in the given + * DRM device, so we can avoid re-creating it. + * + * \param device The DRM device in which this colorop blob is stored. + * \param xform The xform from which the LUT comes from. This object matches its + * lifetime. + * \param curve_step What xform curve step originated the 3x1D LUT. + * \param quantization The colorop 3x1D LUT quantization (U32 or U16). + * \param cm_lut The 3x1D LUT from which the colorop will be created. + * \param lut_len The number of taps for each of the 1D LUT. + * \return The 3x1D LUT colorop blob. + */ +struct drm_colorop_3x1d_lut_blob * +drm_colorop_3x1d_lut_blob_create(struct drm_device *device, + struct weston_color_transform *xform, + enum weston_color_curve_step curve_step, + enum drm_colorop_3x1d_lut_blob_quantization quantization, + struct weston_vec3f *cm_lut, uint32_t lut_len) +{ + struct drm_backend *b = device->backend; + struct drm_colorop_3x1d_lut_blob *lut; + uint32_t blob_id; + unsigned int i; + int ret = -1; + + switch (quantization) { + case DRM_COLOROP_3X1D_LUT_BLOB_QUANTIZATION_U16: { + struct drm_color_lut *drm_lut = + xcalloc(lut_len, sizeof(*drm_lut)); + + for (i = 0; i < lut_len; i++) + drm_lut[i] = drm_vec3f_to_u16(cm_lut[i]); + + ret = drmModeCreatePropertyBlob(device->kms_device->fd, drm_lut, lut_len * sizeof(*drm_lut), + &blob_id); + free(drm_lut); + break; + } + case DRM_COLOROP_3X1D_LUT_BLOB_QUANTIZATION_U32: { + struct drm_color_lut32 *drm_lut = + xcalloc(lut_len, sizeof(*drm_lut)); + + for (i = 0; i < lut_len; i++) + drm_lut[i] = drm_vec3f_to_u32(cm_lut[i]); + + ret = drmModeCreatePropertyBlob(device->kms_device->fd, drm_lut, lut_len * sizeof(*drm_lut), + &blob_id); + free(drm_lut); + break; + }} + + if (ret < 0) { + drm_debug(b, "[colorop] failed to create blob for colorop 3x1D LUT;\n" \ + " lut_len %u, quantization %s", + lut_len, + quantization == DRM_COLOROP_3X1D_LUT_BLOB_QUANTIZATION_U16 ? "u16" : "u32"); + return NULL; + } + + lut = xzalloc(sizeof(*lut)); + + lut->device = device; + lut->xform = xform; + lut->curve_step = curve_step; + lut->quantization = quantization; + lut->lut_len = lut_len; + lut->blob_id = blob_id; + + wl_list_insert(&device->drm_colorop_3x1d_lut_blob_list, &lut->link); + + lut->destroy_listener.notify = drm_colorop_3x1d_lut_blob_destroy_handler; + wl_signal_add(&lut->xform->destroy_signal, &lut->destroy_listener); + + return lut; +} + +enum lowering_curve_policy { + LOWERING_CURVE_POLICY_ALLOW = true, + LOWERING_CURVE_POLICY_DENY = false, +}; + +static const char * +lowering_curve_policy_str(enum lowering_curve_policy policy) +{ + switch (policy) { + case LOWERING_CURVE_POLICY_DENY: + return "deny lowering curve"; + case LOWERING_CURVE_POLICY_ALLOW: + return "allow lowering curve"; + } + return "???"; +} + +static struct drm_colorop_3x1d_lut_blob * +drm_colorop_3x1d_lut_blob_from_curve(struct drm_device *device, + struct weston_color_transform *xform, + enum weston_color_curve_step curve_step, + uint32_t lut_len) +{ + struct weston_compositor *compositor = xform->cm->compositor; + struct drm_backend *b = device->backend; + struct drm_colorop_3x1d_lut_blob *colorop_lut; + char *err_msg; + struct weston_vec3f *cm_lut; + + /* No need to create, 3x1D LUT colorop already exists. */ + colorop_lut = drm_colorop_3x1d_lut_blob_search(device, xform, curve_step, + DRM_COLOROP_3X1D_LUT_BLOB_QUANTIZATION_U32, + lut_len); + if (colorop_lut) + return colorop_lut; + + cm_lut = weston_color_curve_to_3x1D_LUT(compositor, xform, curve_step, + WESTON_COLOR_PRECISION_CARELESS, + lut_len, &err_msg); + if (!cm_lut) { + drm_debug(b, "[colorop] failed to create colorop 3x1D from curve: %s\n", + err_msg); + free(err_msg); + return NULL; + } + + colorop_lut = + drm_colorop_3x1d_lut_blob_create(device, xform, curve_step, + DRM_COLOROP_3X1D_LUT_BLOB_QUANTIZATION_U32, + cm_lut, lut_len); + free(cm_lut); + if (!colorop_lut) { + drm_debug(b, "[colorop] failed to create colorop 3x1D from curve\n"); + return NULL; + } + + return colorop_lut; +} + +static void +drm_colorop_clut_blob_destroy(struct drm_colorop_clut_blob *clut) +{ + wl_list_remove(&clut->destroy_listener.link); + wl_list_remove(&clut->link); + drmModeDestroyPropertyBlob(clut->device->kms_device->fd, clut->clut_blob_id); + drmModeDestroyPropertyBlob(clut->device->kms_device->fd, clut->shaper_blob_id); + free(clut); +} + +static void +drm_colorop_clut_blob_destroy_handler(struct wl_listener *l, void *data) +{ + struct drm_colorop_clut_blob *clut; + + clut = wl_container_of(l, clut, destroy_listener); + assert(clut->xform == data); + + drm_colorop_clut_blob_destroy(clut); +} + +static struct drm_colorop_clut_blob * +drm_colorop_clut_blob_create(struct drm_device *device, + struct weston_color_transform *xform, + uint32_t len_shaper, float *cm_shaper, + uint32_t len_clut, float *cm_clut) +{ + struct drm_backend *b = device->backend; + struct drm_colorop_clut_blob *clut; + struct drm_color_lut32 *drm_clut; + struct drm_color_lut32 *drm_shaper; + uint32_t clut_blob_id, shaper_blob_id; + struct weston_vec3f v; + unsigned int i, index_r, index_g, index_b, index; + int ret; + + drm_clut = xcalloc(len_clut * len_clut * len_clut, sizeof(*drm_clut)); + drm_shaper = xcalloc(len_shaper, sizeof(*drm_shaper)); + + for (i = 0; i < len_shaper; i++) { + v = WESTON_VEC3F(cm_shaper[i], + cm_shaper[i + len_shaper], + cm_shaper[i + 2 * len_shaper]); + drm_shaper[i] = drm_vec3f_to_u32(v); + } + + ret = drmModeCreatePropertyBlob(device->kms_device->fd, drm_shaper, + len_shaper * sizeof(*drm_shaper), + &shaper_blob_id); + if (ret < 0) { + drm_debug(b, "[colorop] failed to create blob for colorop shaper\n"); + goto out; + } + + /** + * Kernel uAPI doc states that the KMS 3D LUT indexes are traversed in + * BGR order (R index growing first, then G and lastly B). Our 3D cLUT + * is traversed in BGR order as well, so no index mapping is required. + */ + for (index_b = 0; index_b < len_clut; index_b++) { + for (index_g = 0; index_g < len_clut; index_g++) { + for (index_r = 0; index_r < len_clut; index_r++) { + index = index_r + len_clut * (index_g + len_clut * index_b); + v = WESTON_VEC3F(cm_clut[3 * index], + cm_clut[3 * index + 1], + cm_clut[3 * index + 2]); + drm_clut[index] = drm_vec3f_to_u32(v); + } + } + } + ret = drmModeCreatePropertyBlob(device->kms_device->fd, drm_clut, + len_clut * len_clut * len_clut * sizeof(*drm_clut), + &clut_blob_id); + if (ret < 0) { + drmModeDestroyPropertyBlob(device->kms_device->fd, shaper_blob_id); + drm_debug(b, "[colorop] failed to create blob for colorop 3D cLUT\n"); + goto out; + } + +out: + free(drm_clut); + free(drm_shaper); + + if (ret < 0) + return NULL; + + clut = xzalloc(sizeof(*clut)); + + clut->device = device; + clut->xform = xform; + clut->shaper_len = len_shaper; + clut->clut_len = len_clut; + clut->shaper_blob_id = shaper_blob_id; + clut->clut_blob_id = clut_blob_id; + + wl_list_insert(&device->drm_colorop_clut_blob_list, &clut->link); + + clut->destroy_listener.notify = drm_colorop_clut_blob_destroy_handler; + wl_signal_add(&xform->destroy_signal, &clut->destroy_listener); + + return clut; +} + +static struct drm_colorop_clut_blob * +drm_colorop_clut_blob_search(struct drm_device *device, + struct weston_color_transform *xform, + uint32_t clut_len, uint32_t shaper_len) +{ + struct drm_colorop_clut_blob *clut; + + wl_list_for_each(clut, &device->drm_colorop_clut_blob_list, link) + if (clut->xform == xform && + clut->clut_len == clut_len && clut->shaper_len == shaper_len) + return clut; + + return NULL; +} + +static struct drm_colorop_clut_blob * +drm_colorop_clut_blob_from_xform(struct drm_device *device, + struct weston_color_transform *xform, + uint32_t len_shaper, uint32_t len_clut) +{ + float *cm_shaper = NULL; + float *cm_clut = NULL; + struct drm_colorop_clut_blob *colorop_clut = NULL; + + colorop_clut = drm_colorop_clut_blob_search(device, xform, len_shaper, len_clut); + if (colorop_clut) + return colorop_clut; + + /* Get shaper + 3D cLUT from xform. */ + cm_shaper = xcalloc(3 * len_shaper, sizeof(*cm_shaper)); + cm_clut = xcalloc(3 * len_clut * len_clut * len_clut, sizeof(*cm_clut)); + if (!xform->to_clut(xform, len_shaper, cm_shaper, len_clut, cm_clut)) + goto out; + + colorop_clut = drm_colorop_clut_blob_create(device, xform, + len_shaper, cm_shaper, + len_clut, cm_clut); + +out: + free(cm_shaper); + free(cm_clut); + + return colorop_clut; +} + +static void +drm_colorop_matrix_blob_destroy(struct drm_colorop_matrix_blob *mat) +{ + wl_list_remove(&mat->destroy_listener.link); + wl_list_remove(&mat->link); + drmModeDestroyPropertyBlob(mat->device->kms_device->fd, mat->blob_id); + free(mat); +} + +static void +drm_colorop_matrix_blob_destroy_handler(struct wl_listener *l, void *data) +{ + struct drm_colorop_matrix_blob *mat = + wl_container_of(l, mat, destroy_listener); + + drm_colorop_matrix_blob_destroy(mat); +} + +static struct drm_colorop_matrix_blob * +drm_colorop_matrix_blob_search(struct drm_device *device, + struct weston_color_transform *xform) +{ + struct drm_colorop_matrix_blob *mat; + + wl_list_for_each(mat, &device->drm_colorop_matrix_blob_list, link) + if (mat->xform == xform) + return mat; + + return NULL; +} + +/** + * Float to S31.32 sign-magnitude representation. + */ +static uint64_t +float_to_s31_32_sign_magnitude(float val) +{ + uint64_t ret; + + if (val < 0) { + ret = (uint64_t) (-val * (1ULL << 32)); + ret |= 1ULL << 63; + } else { + ret = (uint64_t) (val * (1ULL << 32)); + } + + return ret; +} + +static struct drm_colorop_matrix_blob * +drm_colorop_matrix_blob_create(struct drm_device *device, + struct weston_color_transform *xform, + struct drm_color_ctm_3x4 *matrix) +{ + struct drm_backend *b = device->backend; + struct drm_colorop_matrix_blob *colorop_mat; + uint32_t blob_id; + int ret; + + ret = drmModeCreatePropertyBlob(device->kms_device->fd, matrix, + sizeof(*matrix), &blob_id); + if (ret < 0) { + drm_debug(b, "[colorop] failed to create blob for matrix\n"); + return NULL; + } + + colorop_mat = xzalloc(sizeof(*colorop_mat)); + + colorop_mat->blob_id = blob_id; + colorop_mat->device = device; + colorop_mat->xform = xform; + wl_list_insert(&device->drm_colorop_matrix_blob_list, &colorop_mat->link); + colorop_mat->destroy_listener.notify = drm_colorop_matrix_blob_destroy_handler; + wl_signal_add(&xform->destroy_signal, &colorop_mat->destroy_listener); + + return colorop_mat; +} + +static struct drm_colorop_matrix_blob * +drm_colorop_matrix_blob_from_mapping(struct drm_device *device, + struct weston_color_transform *xform) +{ + struct drm_backend *b = device->backend; + struct weston_color_mapping *mapping = &xform->mapping; + struct drm_colorop_matrix_blob *colorop_mat; + struct drm_color_ctm_3x4 *mat_3x4; + unsigned int row, col; + float val; + + /* No need to create, colorop matrix already exists. */ + colorop_mat = drm_colorop_matrix_blob_search(device, xform); + if (colorop_mat) + return colorop_mat; + + mat_3x4 = xzalloc(sizeof(*mat_3x4)); + + /** + * mapping->u.mat.matrix is in column-major order. We transpose it and + * also add a new column with the offset. Also, kernel requires the + * values in S31.32 sign-magnitude representation. + */ + for (row = 0; row < 3; row++) { + for (col = 0; col < 3; col++) { + val = mapping->u.mat.matrix.col[col].el[row]; + mat_3x4->matrix[row * 4 + col] = float_to_s31_32_sign_magnitude(val); + } + val = mapping->u.mat.offset.el[row]; + mat_3x4->matrix[row * 4 + 3] = float_to_s31_32_sign_magnitude(val); + } + + colorop_mat = drm_colorop_matrix_blob_create(device, xform, mat_3x4); + free(mat_3x4); + if (!colorop_mat) { + drm_debug(b, "[colorop] failed to create colorop matrix from mapping\n"); + return NULL; + } + + return colorop_mat; +} + +struct colorop_curve_scaler { + float factor; + /* placement wrt the curve colorop in the chain */ + enum { + PLACEMENT_NONE = 0, + PLACEMENT_BEFORE, + PLACEMENT_AFTER, + } placement; +}; + +static enum wdrm_colorop_curve_1d +weston_tf_to_colorop_curve(const struct weston_color_tf_info *tf_info, + enum weston_tf_direction tf_direction, + struct colorop_curve_scaler *scaler) +{ + /** + * wdrm_colorop_curve_1d only supports PQ EOTF (and its inverse) scaled + * by 125. We don't have a tf_info that corresponds to this specific + * scaled curve, but we handle it as a special case. A multiplier + * colorop is needed to scale values up or down, depending if we have + * the EOTF or its inverse. See curve_create_colorop_state(). + */ + if (tf_info->tf == WESTON_TF_ST2084_PQ) { + if (tf_direction == WESTON_INVERSE_TF) { + scaler->factor = 125.0f; + scaler->placement = PLACEMENT_BEFORE; + return WDRM_COLOROP_CURVE_1D_PQ_125_INV_EOTF; + } else { + scaler->factor = 1.0f / 125.0f; + scaler->placement = PLACEMENT_AFTER; + return WDRM_COLOROP_CURVE_1D_PQ_125_EOTF; + } + } + + scaler->factor = 1.0f; + scaler->placement = PLACEMENT_NONE; + return (tf_direction == WESTON_INVERSE_TF) ? + tf_info->kms_colorop_inverse : tf_info->kms_colorop; +} + +static void +drm_colorop_destroy(struct drm_colorop *colorop) +{ + wl_list_remove(&colorop->link); + drm_property_info_free(colorop->props, WDRM_COLOROP__COUNT); + + free(colorop); +} + +static struct drm_colorop * +drm_colorop_create(struct drm_color_pipeline *pipeline, uint32_t colorop_id, + uint32_t *next_colorop_id) +{ + struct drm_device *device = pipeline->plane->device; + drmModeObjectPropertiesPtr props_drm; + struct drm_colorop *colorop; + + *next_colorop_id = 0; + + props_drm = drmModeObjectGetProperties(device->kms_device->fd, colorop_id, + DRM_MODE_OBJECT_COLOROP); + if (!props_drm) + return NULL; + + colorop = xzalloc(sizeof(*colorop)); + + wl_list_insert(pipeline->colorop_list.prev, &colorop->link); + + colorop->id = colorop_id; + colorop->pipeline = pipeline; + + drm_property_info_populate(device, colorop_props, colorop->props, + WDRM_COLOROP__COUNT, props_drm); + + colorop->type = drm_property_get_value(&colorop->props[WDRM_COLOROP_TYPE], + props_drm, WDRM_COLOROP_TYPE__COUNT); + if (colorop->type == WDRM_COLOROP_TYPE__COUNT) { + drm_colorop_destroy(colorop); + drmModeFreeObjectProperties(props_drm); + return NULL; + } + + colorop->size = drm_property_get_value(&colorop->props[WDRM_COLOROP_SIZE], + props_drm, 0); + if (colorop->size == 0 && (colorop->type == WDRM_COLOROP_TYPE_1D_LUT || + colorop->type == WDRM_COLOROP_TYPE_3D_LUT)) { + drm_colorop_destroy(colorop); + drmModeFreeObjectProperties(props_drm); + return NULL; + } + + colorop->can_bypass = (colorop->props[WDRM_COLOROP_BYPASS].prop_id != 0); + + *next_colorop_id = + drm_property_get_value(&colorop->props[WDRM_COLOROP_NEXT], + props_drm, 0); + + drmModeFreeObjectProperties(props_drm); + + return colorop; +} + +/** + * Given a colorop this returns its type as a string. + * + * \param colorop The colorop. + * \return The colorop type as a string. + */ +const char * +drm_colorop_type_to_str(struct drm_colorop *colorop) +{ + return colorop->props[WDRM_COLOROP_TYPE].enum_values[colorop->type].name; +} + +static struct drm_colorop * +drm_colorop_iterate(struct drm_color_pipeline *pipeline, struct drm_colorop *iter) +{ + struct wl_list *list = &pipeline->colorop_list; + struct wl_list *node; + + if (iter) + node = iter->link.next; + else + node = list->next; + + if (node == list) + return NULL; + + return container_of(node, struct drm_colorop, link); +} + +static bool +is_colorop_compatible_with_curve(struct weston_compositor *compositor, + struct drm_colorop *colorop, + struct weston_color_curve *curve) +{ + struct weston_color_curve_parametric param; + struct drm_property_info *prop_info; + enum wdrm_colorop_curve_1d curve_type; + struct colorop_curve_scaler scaler; + bool ret; + + if (colorop->type == WDRM_COLOROP_TYPE_1D_CURVE) { + if (curve->type != WESTON_COLOR_CURVE_TYPE_ENUM) + return false; + + curve_type = weston_tf_to_colorop_curve(curve->u.enumerated.tf.info, + curve->u.enumerated.tf_direction, + &scaler); + if (curve_type == WDRM_COLOROP_CURVE_1D__COUNT) + return false; + + prop_info = &colorop->props[WDRM_COLOROP_CURVE_1D]; + if (!prop_info->enum_values[curve_type].valid) + return false; + + return true; + } else if (colorop->type == WDRM_COLOROP_TYPE_1D_LUT) { + switch (curve->type) { + case WESTON_COLOR_CURVE_TYPE_LUT_3x1D: + return true; + case WESTON_COLOR_CURVE_TYPE_PARAMETRIC: + /* Parametric can be lowered to LUT. */ + return true; + case WESTON_COLOR_CURVE_TYPE_ENUM: + switch (curve->u.enumerated.tf.info->tf) { + case WESTON_TF_ST2084_PQ: + /* This TF is implemented, so we can lower curve to LUT. */ + return true; + default: + /* If we can lower the TF to parametric, we can use it + * to create a LUT. */ + ret = weston_color_curve_enum_get_parametric(compositor, + &curve->u.enumerated, + ¶m); + return ret; + } + case WESTON_COLOR_CURVE_TYPE_IDENTITY: + /* Dead code, function never called for IDENTITY. */ + weston_assert_not_reached(compositor, + "no need to get colorop for identity curve"); + } + + return false; + } + + return false; +} + +static struct drm_colorop * +search_colorop_compatible_curve(struct drm_color_pipeline *pipeline, + struct drm_colorop *previous_colorop, + struct weston_color_curve *curve, + enum lowering_curve_policy policy) +{ + struct drm_backend *b = pipeline->plane->device->backend; + struct drm_colorop *colorop = previous_colorop; + + /** + * Identity curve should not need a colorop, so calling this func for + * IDENTITY is not allowed. + */ + weston_assert_u32_ne(b->compositor, + curve->type, WESTON_COLOR_CURVE_TYPE_IDENTITY); + + while ((colorop = drm_colorop_iterate(pipeline, colorop))) { + switch (curve->type) { + case WESTON_COLOR_CURVE_TYPE_ENUM: + if (colorop->type == WDRM_COLOROP_TYPE_1D_CURVE && + is_colorop_compatible_with_curve(b->compositor, colorop, curve)) + return colorop; + else if (colorop->type == WDRM_COLOROP_TYPE_1D_LUT && + policy == LOWERING_CURVE_POLICY_ALLOW && + is_colorop_compatible_with_curve(b->compositor, colorop, curve)) + return colorop; + break; + case WESTON_COLOR_CURVE_TYPE_PARAMETRIC: + case WESTON_COLOR_CURVE_TYPE_LUT_3x1D: + if (colorop->type == WDRM_COLOROP_TYPE_1D_LUT) + return colorop; + break; + case WESTON_COLOR_CURVE_TYPE_IDENTITY: + /* Dead code. */ + weston_assert_not_reached(b->compositor, + "no need to get colorop for identity curve"); + } + + if (!colorop->can_bypass) + break; + } + + return NULL; +} + +static struct drm_colorop * +search_colorop_type(struct drm_color_pipeline *pipeline, + struct drm_colorop *previous_colorop, + enum wdrm_colorop_type type) +{ + struct drm_colorop *colorop = previous_colorop; + + while ((colorop = drm_colorop_iterate(pipeline, colorop))) { + if (colorop->type == type) + return colorop; + + if (!colorop->can_bypass) + break; + } + + return NULL; +} + +static struct drm_colorop_state * +drm_colorop_state_create(struct drm_color_pipeline_state *pipeline_state, + struct drm_colorop *colorop, + struct drm_colorop_state_object so) +{ + struct drm_colorop_state *colorop_state; + + colorop_state = xzalloc(sizeof(*colorop_state)); + + wl_list_insert(pipeline_state->colorop_state_list.prev, &colorop_state->link); + + colorop_state->colorop = colorop; + colorop_state->object = so; + + return colorop_state; +} + +static void +drm_colorop_state_destroy(struct drm_colorop_state *colorop_state) +{ + wl_list_remove(&colorop_state->link); + free(colorop_state); +} + +static struct drm_color_pipeline_state * +drm_color_pipeline_state_create(struct drm_color_pipeline *pipeline) +{ + struct drm_color_pipeline_state *state; + + state = xzalloc(sizeof(*state)); + + state->pipeline = pipeline; + + wl_list_init(&state->colorop_state_list); + + return state; +} + +/** + * Destroys a color pipeline state. + * + * @param state The pipeline state to destroy. + */ +void +drm_color_pipeline_state_destroy(struct drm_color_pipeline_state *state) +{ + struct drm_colorop_state *colorop_state, *tmp_colorop_state; + + if (!state) + return; + + wl_list_for_each_safe(colorop_state, tmp_colorop_state, + &state->colorop_state_list, link) + drm_colorop_state_destroy(colorop_state); + + free(state); +} + +static uint64_t +prop_val_from_curve(struct drm_device *device, struct drm_colorop *colorop, + struct weston_color_curve *curve) +{ + struct weston_compositor *compositor = device->backend->compositor; + enum wdrm_colorop_curve_1d curve_type; + struct drm_property_enum_info *prop_info; + struct colorop_curve_scaler scaler; + + weston_assert_u32_eq(compositor, curve->type, + WESTON_COLOR_CURVE_TYPE_ENUM); + + curve_type = weston_tf_to_colorop_curve(curve->u.enumerated.tf.info, + curve->u.enumerated.tf_direction, + &scaler); + weston_assert_u32_ne(compositor, curve_type, + WDRM_COLOROP_CURVE_1D__COUNT); + + prop_info = &colorop->props[WDRM_COLOROP_CURVE_1D].enum_values[curve_type]; + weston_assert_true(compositor, prop_info->valid); + + return prop_info->value; +} + +static struct drm_colorop_state * +multiplier_create_colorop_state(struct drm_color_pipeline_state *pipeline_state, + struct drm_colorop *first_colorop, + struct drm_colorop *last_colorop, + float multiplier) +{ + struct drm_color_pipeline *pipeline = pipeline_state->pipeline; + struct drm_colorop_state_object so = { 0 }; + struct drm_colorop *colorop; + bool found = false; + + /** + * The multiplier colorop must be between first_colorop and + * last_colorop (excluding both). + */ + colorop = first_colorop; + while ((colorop = drm_colorop_iterate(pipeline, colorop))) { + if (colorop == last_colorop) + break; + + if (colorop->type == WDRM_COLOROP_TYPE_MULTIPLIER) { + found = true; + break; + } + } + if (!found) + return NULL; + + so.type = COLOROP_OBJECT_TYPE_MULTIPLIER; + so.multiplier = (double) multiplier * (1ULL << 32); + + return drm_colorop_state_create(pipeline_state, colorop, so); +} + +static struct drm_colorop_state * +curve_create_colorop_state(struct drm_color_pipeline_state *pipeline_state, + struct drm_colorop *previous_colorop, + struct weston_color_transform *xform, + enum weston_color_curve_step curve_step, + enum lowering_curve_policy policy) +{ + struct drm_color_pipeline *pipeline = pipeline_state->pipeline; + struct weston_compositor *compositor = pipeline->plane->base.compositor; + struct drm_device *device = pipeline->plane->device; + struct drm_colorop_3x1d_lut_blob *lut_blob; + struct weston_color_curve *curve; + struct drm_colorop_state_object so = { 0 }; + struct drm_colorop *colorop_curve; + struct drm_colorop_state *cs_curve, *cs_multiplier; + uint32_t lut_len; + struct colorop_curve_scaler scaler = (struct colorop_curve_scaler) { + .factor = 1.0f, + .placement = PLACEMENT_NONE, + }; + + curve = (curve_step == WESTON_COLOR_CURVE_STEP_PRE) ? &xform->pre_curve : + &xform->post_curve; + + if (curve->type == WESTON_COLOR_CURVE_TYPE_ENUM) + (void) weston_tf_to_colorop_curve(curve->u.enumerated.tf.info, + curve->u.enumerated.tf_direction, + &scaler); + + colorop_curve = search_colorop_compatible_curve(pipeline, previous_colorop, + curve, policy); + if (!colorop_curve) + return NULL; + + switch (colorop_curve->type) { + case WDRM_COLOROP_TYPE_1D_CURVE: + so.type = COLOROP_OBJECT_TYPE_CURVE; + so.curve_type_prop_val = prop_val_from_curve(device, colorop_curve, curve); + break; + case WDRM_COLOROP_TYPE_1D_LUT: + lut_len = colorop_curve->size; + lut_blob = drm_colorop_3x1d_lut_blob_from_curve(device, xform, + curve_step, lut_len); + if (!lut_blob) + return NULL; + so.type = COLOROP_OBJECT_TYPE_3x1D_LUT; + so.lut_3x1d_blob_id = lut_blob->blob_id; + break; + default: + weston_assert_not_reached(compositor, + "curve colorop should be 1D curve or 1D LUT"); + } + + /** + * Curve may require a multiplier colorop before or after it. + */ + + if (scaler.placement == PLACEMENT_BEFORE) + cs_multiplier = multiplier_create_colorop_state(pipeline_state, + previous_colorop, /* first colorop */ + colorop_curve, /* last colorop */ + scaler.factor); + + cs_curve = drm_colorop_state_create(pipeline_state, colorop_curve, so); + + if (scaler.placement == PLACEMENT_AFTER) + cs_multiplier = multiplier_create_colorop_state(pipeline_state, + colorop_curve, /* first colorop */ + NULL, /* last colorop */ + scaler.factor); + + if (scaler.placement != PLACEMENT_NONE && !cs_multiplier) { + drm_colorop_state_destroy(cs_curve); + return NULL; + } + + /* Return the colorop state of the colorop that comes later in the chain. */ + return (scaler.placement == PLACEMENT_AFTER) ? cs_multiplier : cs_curve; +} + +static struct drm_colorop_state * +mapping_create_colorop_state(struct drm_color_pipeline_state *pipeline_state, + struct drm_colorop *previous_colorop, + struct weston_color_transform *xform) +{ + struct drm_color_pipeline *pipeline = pipeline_state->pipeline; + struct weston_compositor *compositor = pipeline->plane->base.compositor; + struct drm_device *device = pipeline->plane->device; + struct weston_color_mapping *mapping = &xform->mapping; + struct drm_colorop_matrix_blob *mat_blob; + struct drm_colorop_state_object so = { 0 }; + struct drm_colorop *colorop; + + /* For now Weston has only matrices color mapping. */ + weston_assert_u32_eq(compositor, + mapping->type, WESTON_COLOR_MAPPING_TYPE_MATRIX); + + colorop = search_colorop_type(pipeline, previous_colorop, + WDRM_COLOROP_TYPE_CTM_3X4); + if (!colorop) + return NULL; + + mat_blob = drm_colorop_matrix_blob_from_mapping(device, xform); + if (!mat_blob) + return NULL; + + so.type = COLOROP_OBJECT_TYPE_MATRIX; + so.matrix_blob_id = mat_blob->blob_id; + + return drm_colorop_state_create(pipeline_state, colorop, so); +} + +static struct drm_color_pipeline_state * +drm_color_pipeline_state_from_xform_steps(struct drm_color_pipeline *pipeline, + struct weston_color_transform *xform, + enum lowering_curve_policy policy, + const char *indent) +{ + struct drm_backend *b = pipeline->plane->device->backend; + struct drm_color_pipeline_state *pipeline_state; + struct drm_colorop_state *colorop_state; + struct drm_colorop *previous_colorop; + uint32_t type; + + pipeline_state = drm_color_pipeline_state_create(pipeline); + + /* First previous_colorop: none. */ + previous_colorop = NULL; + + /* Find colorop for pre-curve. */ + type = xform->pre_curve.type; + if (type != WESTON_COLOR_CURVE_TYPE_IDENTITY) { + colorop_state = curve_create_colorop_state(pipeline_state, + previous_colorop, xform, + WESTON_COLOR_CURVE_STEP_PRE, + policy); + if (!colorop_state) + goto err; + + previous_colorop = colorop_state->colorop; + } + + /* Find colorop for color mapping. */ + type = xform->mapping.type; + if (type != WESTON_COLOR_MAPPING_TYPE_IDENTITY) { + colorop_state = mapping_create_colorop_state(pipeline_state, + previous_colorop, xform); + if (!colorop_state) + goto err; + + previous_colorop = colorop_state->colorop; + } + + /* Find colorop for post-curve. */ + type = xform->post_curve.type; + if (type != WESTON_COLOR_CURVE_TYPE_IDENTITY) { + colorop_state = curve_create_colorop_state(pipeline_state, + previous_colorop, xform, + WESTON_COLOR_CURVE_STEP_POST, + policy); + if (!colorop_state) + goto err; + } + + drm_debug(b, "%s[colorop] color pipeline id %u IS compatible with xform t%u;\n" \ + "%s policy: %s\n", + indent, pipeline->id, xform->id, indent, + lowering_curve_policy_str(policy)); + return pipeline_state; + +err: + drm_color_pipeline_state_destroy(pipeline_state); + drm_debug(b, "%s[colorop] color pipeline id %u NOT compatible with xform t%u;\n" \ + "%s policy: %s\n", + indent, pipeline->id, xform->id, indent, + lowering_curve_policy_str(policy)); + return NULL; +} + +static struct drm_color_pipeline_state * +drm_color_pipeline_state_from_xform_decomposed(struct drm_color_pipeline *pipeline, + struct weston_color_transform *xform, + const char *indent) +{ + struct drm_device *device = pipeline->plane->device; + struct drm_backend *b = device->backend; + struct drm_color_pipeline_state *pipeline_state = NULL; + struct drm_colorop *colorop_shaper, *colorop_clut; + struct drm_colorop_state_object so_clut = { 0 }; + struct drm_colorop_state_object so_shaper = { 0 }; + struct drm_colorop_clut_blob *clut; + + /* Find colorop for shaper (3x1D LUT). */ + colorop_shaper = search_colorop_type(pipeline, + NULL, /* previous colorop (none) */ + WDRM_COLOROP_TYPE_1D_LUT); + if (!colorop_shaper) + goto out; + + /* Find colorop for 3D cLUT. */ + colorop_clut = search_colorop_type(pipeline, + colorop_shaper, /* previous colorop */ + WDRM_COLOROP_TYPE_3D_LUT); + if (!colorop_clut) + goto out; + + clut = drm_colorop_clut_blob_from_xform(device, xform, + colorop_shaper->size, + colorop_clut->size); + if (!clut) + goto out; + + /* Create pipeline state and fill with the colorops. */ + pipeline_state = drm_color_pipeline_state_create(pipeline); + + so_shaper.type = COLOROP_OBJECT_TYPE_3x1D_LUT; + so_shaper.lut_3x1d_blob_id = clut->shaper_blob_id; + drm_colorop_state_create(pipeline_state, colorop_shaper, so_shaper); + + so_clut.type = COLOROP_OBJECT_TYPE_3D_LUT; + so_clut.lut_3d_blob_id = clut->clut_blob_id; + drm_colorop_state_create(pipeline_state, colorop_clut, so_clut); + +out: + drm_debug(b, "%s[colorop] color pipeline id %u %s compatible with xform id %u;\n" \ + "%s xform decomposed into shaper + 3D LUT\n", + indent, pipeline->id, + pipeline_state ? "IS" : "NOT", + xform->id, indent); + return pipeline_state; +} + +/** + * Given a color transformation, returns a color pipeline state that can + * be used to offload such xform to KMS. + * + * @param plane The DRM plane that we plan to use to offload the view. + * @param xform The xform to offload. + * @param indent To print debug error messages with proper indentation. + * @return The color pipeline state, or NULL if no color pipelines are + * compatible with the xform. + */ +struct drm_color_pipeline_state * +drm_color_pipeline_state_from_xform(struct drm_plane *plane, + struct weston_color_transform *xform, + const char *indent) +{ + struct drm_backend *b = plane->device->backend; + struct drm_color_pipeline_state *pipeline_state; + unsigned int i, mode_index; + enum lowering_curve_policy policy; + enum lowering_curve_policy policy_modes[2] = { + LOWERING_CURVE_POLICY_DENY, LOWERING_CURVE_POLICY_ALLOW + }; + + drm_debug(b, "%s[colorop] searching color pipeline compatible with xform t%u\n", + indent, xform->id); + + /** + * Try to find a compatible pipeline. + * + * First, we try to find a compatible pipeline but not allowing Weston + * enumerated color curves to be lowered to parametric. If we can't find + * something, we start allowing that. + */ + if (xform->steps_valid) { + for (mode_index = 0; mode_index < ARRAY_LENGTH(policy_modes); mode_index++) { + policy = policy_modes[mode_index]; + for (i = 0; i < plane->num_color_pipelines; i++) { + pipeline_state = + drm_color_pipeline_state_from_xform_steps(&plane->pipelines[i], + xform, policy, indent); + if (pipeline_state) + return pipeline_state; + } + } + } + + /** + * Either the pipelines are not compatible with our xform or we were + * unable to optimize the xform to steps. Our last resource would be + * crafting a shaper + 3D LUT from the xform. Let's check if any + * pipelines would be able to handle that. + */ + for (i = 0; i < plane->num_color_pipelines; i++) { + pipeline_state = + drm_color_pipeline_state_from_xform_decomposed(&plane->pipelines[i], + xform, indent); + if (pipeline_state) + return pipeline_state; + } + + return NULL; +} + +static void +drm_color_pipeline_print(struct drm_color_pipeline *pipeline, FILE *fp) +{ + struct drm_colorop *colorop; + struct drm_property_info *curve_props; + const char *type; + const char *sep = " "; + unsigned int i; + + if (!fp) + return; + + fprintf(fp, "[colorop] color pipeline %u (owned by plane %u):\n", + pipeline->id, pipeline->plane->plane_id); + + wl_list_for_each(colorop, &pipeline->colorop_list, link) { + type = drm_colorop_type_to_str(colorop); + + fprintf(fp, "%s[colorop] id %u, type %s, can bypass? %s", + sep, colorop->id, type, yesno(colorop->can_bypass)); + + if (colorop->type == WDRM_COLOROP_TYPE_1D_CURVE) { + curve_props = &colorop->props[WDRM_COLOROP_CURVE_1D]; + for (i = 0; i < curve_props->num_enum_values; i++) { + if (curve_props->enum_values[i].valid) + fprintf(fp, " [%s]", + curve_props->enum_values[i].name); + } + } + + fprintf(fp, "\n"); + } +} + +/** + * Populates the color pipelines of a DRM plane. + * + * This does nothing if the driver does not support color pipelines. + * + * @param plane The DRM plane whose pipelines this populates. + * @param plane_props The DRM plane's props. + */ +void +drm_plane_populate_color_pipelines(struct drm_plane *plane, + drmModeObjectPropertiesPtr plane_props) +{ + struct weston_compositor *compositor = plane->base.compositor; + struct drm_device *device = plane->device; + struct drm_backend *b = device->backend; + FILE *dbg = weston_log_scope_stream(b->debug); + drmModePropertyRes *color_pipeline_props; + uint32_t pipeline_i; + unsigned int i; + + if (plane->props[WDRM_PLANE_COLOR_PIPELINE].prop_id == 0) + return; + + color_pipeline_props = + drmModeGetProperty(device->kms_device->fd, + plane->props[WDRM_PLANE_COLOR_PIPELINE].prop_id); + if (!color_pipeline_props) { + drm_debug(b, "failed to get color pipeline property for plane %u\n", + plane->plane_id); + return; + } + + plane->num_color_pipelines = 0; + for (i = 0; (int)i < color_pipeline_props->count_enums; i++) { + if (color_pipeline_props->enums[i].value != 0) + plane->num_color_pipelines++; + } + plane->pipelines = xzalloc(plane->num_color_pipelines * + sizeof(*plane->pipelines)); + plane->pipeline_props_id = color_pipeline_props->prop_id; + + /* Populate pipelines. */ + pipeline_i = 0; + for (i = 0; (int)i < color_pipeline_props->count_enums; i++) { + struct drm_color_pipeline *pipeline; + struct drm_colorop *colorop; + uint32_t colorop_id, next_colorop_id; + + /* First colorop. */ + colorop_id = color_pipeline_props->enums[i].value; + if (colorop_id == 0) + continue; + + pipeline = &plane->pipelines[pipeline_i++]; + + pipeline->plane = plane; + wl_list_init(&pipeline->colorop_list); + + /* Id of the pipeline is the same of its first colorop. */ + pipeline->id = colorop_id; + + while (colorop_id != 0) { + colorop = drm_colorop_create(pipeline, colorop_id, &next_colorop_id); + if (!colorop) { + drm_debug(b, "[colorop] failed to create colorop for id %u, destroying color pipelines for plane %u\n", + colorop_id, plane->plane_id); + drm_plane_release_color_pipelines(plane); + goto out; + } + colorop_id = next_colorop_id; + } + + weston_assert_list_not_empty(compositor, &pipeline->colorop_list); + if (dbg) { + drm_color_pipeline_print(pipeline, dbg); + fflush(dbg); + } + } + weston_assert_u32_eq(b->compositor, + plane->num_color_pipelines, pipeline_i); + +out: + drmModeFreeProperty(color_pipeline_props); +} + +/** + * Release the color pipelines of a drm plane. + * + * @param plane The drm plane whose pipelines should be released. + */ +void +drm_plane_release_color_pipelines(struct drm_plane *plane) +{ + struct drm_color_pipeline *pipeline; + struct drm_colorop *colorop, *tmp; + unsigned int i; + + for (i = 0; i < plane->num_color_pipelines; i++) { + pipeline = &plane->pipelines[i]; + wl_list_for_each_safe(colorop, tmp, &pipeline->colorop_list, link) + drm_colorop_destroy(colorop); + } + + plane->num_color_pipelines = 0; + free(plane->pipelines); + plane->pipelines = NULL; +} diff --git a/libweston/backend-drm/colorops.h b/libweston/backend-drm/colorops.h new file mode 100644 index 000000000..e0e7edb53 --- /dev/null +++ b/libweston/backend-drm/colorops.h @@ -0,0 +1,204 @@ +/* + * Copyright © 2025-2026 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. + */ + +#pragma once + +#include "drm-internal.h" +#include "drm-kms-enums.h" + +struct drm_colorop_clut_blob { + /* drm_device::drm_colorop_clut_blob_list */ + struct wl_list link; + struct drm_device *device; + + /* Lifetime matches the xform. */ + struct weston_color_transform *xform; + struct wl_listener destroy_listener; + + uint32_t shaper_len; + uint32_t clut_len; + + uint32_t shaper_blob_id; + uint32_t clut_blob_id; +}; + +struct drm_colorop_matrix_blob { + /* drm_device::drm_colorop_matrix_blob_list */ + struct wl_list link; + struct drm_device *device; + + /* Lifetime matches the xform. */ + struct weston_color_transform *xform; + struct wl_listener destroy_listener; + + uint32_t blob_id; +}; + +struct drm_colorop { + struct drm_color_pipeline *pipeline; + struct wl_list link; /* drm_pipeline::colorop_list */ + + enum wdrm_colorop_type type; + + uint32_t id; + + /* Some colorop's can be bypassed. */ + bool can_bypass; + + /* Only useful for 1D and 3D LUT colorop's. */ + uint32_t size; + + /* Holds the properties for the colorop. */ + struct drm_property_info props[WDRM_COLOROP__COUNT]; +}; + +enum colorop_object_type { + COLOROP_OBJECT_TYPE_CURVE = 0, + COLOROP_OBJECT_TYPE_MATRIX, + COLOROP_OBJECT_TYPE_3x1D_LUT, + COLOROP_OBJECT_TYPE_3D_LUT, + COLOROP_OBJECT_TYPE_MULTIPLIER, +}; + +struct drm_colorop_state_object { + /* Defines which of the below is valid. The others are zero. */ + enum colorop_object_type type; + + uint64_t curve_type_prop_val; + uint32_t matrix_blob_id; + uint32_t lut_3x1d_blob_id; + uint32_t lut_3d_blob_id; + uint64_t multiplier; +}; + +struct drm_colorop_state { + struct drm_colorop *colorop; + /* struct drm_color_pipeline_state::colorop_state_list */ + struct wl_list link; + + /* Object that should be programmed through the colorop. */ + struct drm_colorop_state_object object; +}; + +struct drm_color_pipeline { + struct drm_plane *plane; + struct wl_list colorop_list; /* drm_colorop::link */ + uint32_t id; +}; + +struct drm_color_pipeline_state { + struct drm_color_pipeline *pipeline; + + /* struct drm_colorop_state::link */ + struct wl_list colorop_state_list; +}; + +#if CAN_OFFLOAD_COLOR_PIPELINE + +void +drm_color_pipeline_state_destroy(struct drm_color_pipeline_state *state); + +struct drm_color_pipeline_state * +drm_color_pipeline_state_from_xform(struct drm_plane *plane, + struct weston_color_transform *xform, + const char *indent); + +struct drm_colorop_3x1d_lut_blob * +drm_colorop_3x1d_lut_blob_create(struct drm_device *device, + struct weston_color_transform *xform, + enum weston_color_curve_step curve_step, + enum drm_colorop_3x1d_lut_blob_quantization quantization, + struct weston_vec3f *cm_lut, uint32_t lut_len); + +struct drm_colorop_3x1d_lut_blob * +drm_colorop_3x1d_lut_blob_search(struct drm_device *device, + struct weston_color_transform *xform, + enum weston_color_curve_step curve_step, + enum drm_colorop_3x1d_lut_blob_quantization quantization, + uint32_t lut_len); + +const char * +drm_colorop_type_to_str(struct drm_colorop *colorop); + +void +drm_plane_populate_color_pipelines(struct drm_plane *plane, + drmModeObjectPropertiesPtr plane_props); + +void +drm_plane_release_color_pipelines(struct drm_plane *plane); + +#else /* CAN_OFFLOAD_COLOR_PIPELINE */ + +static inline void +drm_color_pipeline_state_destroy(struct drm_color_pipeline_state *state) +{ +} + +static inline struct drm_color_pipeline_state * +drm_color_pipeline_state_from_xform(struct drm_plane *plane, + struct weston_color_transform *xform, + const char *indent) +{ + return NULL; +} + +static inline struct drm_colorop_3x1d_lut_blob * +drm_colorop_3x1d_lut_blob_create(struct drm_device *device, + struct weston_color_transform *xform, + enum weston_color_curve_step curve_step, + enum drm_colorop_3x1d_lut_blob_quantization quantization, + struct weston_vec3f *cm_lut, uint32_t lut_len) +{ + return NULL; +} + +static inline struct drm_colorop_3x1d_lut_blob * +drm_colorop_3x1d_lut_blob_search(struct drm_device *device, + struct weston_color_transform *xform, + enum weston_color_curve_step curve_step, + enum drm_colorop_3x1d_lut_blob_quantization quantization, + uint32_t lut_len) +{ + return NULL; +} + +static inline const char * +drm_colorop_type_to_str(struct drm_colorop *colorop) +{ + return "undefined"; +} + +static inline void +drm_plane_populate_color_pipelines(struct drm_plane *plane, + drmModeObjectPropertiesPtr plane_props) +{ +} + +static inline void +drm_plane_release_color_pipelines(struct drm_plane *plane) +{ +} + +#endif /* CAN_OFFLOAD_COLOR_PIPELINE */ diff --git a/libweston/backend-drm/drm-internal.h b/libweston/backend-drm/drm-internal.h index d4ba085d0..0671c4e1e 100644 --- a/libweston/backend-drm/drm-internal.h +++ b/libweston/backend-drm/drm-internal.h @@ -27,6 +27,8 @@ * SOFTWARE. */ +#pragma once + #include "config.h" #include @@ -227,6 +229,8 @@ struct drm_device { bool aspect_ratio_supported; + bool color_pipeline_supported; + int32_t cursor_width; int32_t cursor_height; @@ -247,8 +251,12 @@ struct drm_device { /* drm_backend::kms_list */ struct wl_list link; - /* struct drm_colorop_3x1d_lut::link */ - struct wl_list drm_colorop_3x1d_lut_list; + /* struct drm_colorop_3x1d_lut_blob::link */ + struct wl_list drm_colorop_3x1d_lut_blob_list; + /* struct drm_colorop_clut_blob::link */ + struct wl_list drm_colorop_clut_blob_list; + /* struct drm_colorop_matrix_blob::link */ + struct wl_list drm_colorop_matrix_blob_list; int reused_state_failures; }; @@ -393,6 +401,30 @@ struct drm_output_state { bool planes_enabled; }; +enum drm_colorop_3x1d_lut_blob_quantization { + DRM_COLOROP_3X1D_LUT_BLOB_QUANTIZATION_U16 = 0, + DRM_COLOROP_3X1D_LUT_BLOB_QUANTIZATION_U32, +}; + +struct drm_colorop_3x1d_lut_blob { + /* drm_device::drm_colorop_3x1d_lut_blob_list */ + struct wl_list link; + struct drm_device *device; + + /* Lifetime matches the xform. */ + struct weston_color_transform *xform; + struct wl_listener destroy_listener; + + /* Which curve of the xform the 3x1D LUT was generated from. */ + enum weston_color_curve_step curve_step; + + enum drm_colorop_3x1d_lut_blob_quantization quantization; + + uint32_t lut_len; + + uint32_t blob_id; +}; + /** * Plane state holds the dynamic state for a plane: where it is positioned, * and which buffer it is currently displaying. @@ -413,6 +445,9 @@ struct drm_plane_state { struct weston_paint_node *paint_node; /**< maintained for drm_assign_planes only */ + /* only when a color transformation is being offloaded */ + struct drm_color_pipeline_state *pipeline_state; + int32_t src_x, src_y; uint32_t src_w, src_h; int32_t dest_x, dest_y; @@ -477,6 +512,10 @@ struct drm_plane { struct wl_list link; struct weston_drm_format_array formats; + + uint32_t pipeline_props_id; + uint32_t num_color_pipelines; + struct drm_color_pipeline *pipelines; }; struct drm_plane_handle { @@ -548,19 +587,6 @@ struct drm_writeback { struct weston_drm_format_array formats; }; -struct drm_colorop_3x1d_lut { - /* drm_device::drm_colorop_3x1d_lut_list */ - struct wl_list link; - struct drm_device *device; - - uint64_t lut_size; - - struct weston_color_transform *xform; - struct wl_listener destroy_listener; - - uint32_t blob_id; -}; - struct drm_head { struct weston_head base; struct drm_connector connector; @@ -643,7 +669,7 @@ struct drm_output { bool legacy_gamma_not_supported; uint16_t legacy_gamma_size; - struct drm_colorop_3x1d_lut *blend_to_output_xform; + struct drm_colorop_3x1d_lut_blob *blend_to_output_xform; /* Plane being displayed directly on the CRTC */ struct drm_plane_handle *scanout_handle; @@ -868,6 +894,7 @@ extern struct drm_property_enum_info content_protection_enums[]; extern struct drm_property_enum_info hdcp_content_type_enums[]; extern const struct drm_property_info connector_props[]; extern const struct drm_property_info crtc_props[]; +extern const struct drm_property_info colorop_props[]; int init_kms_caps(struct drm_device *device); diff --git a/libweston/backend-drm/drm-kms-enums.h b/libweston/backend-drm/drm-kms-enums.h index ad9a11148..04e85d8b7 100644 --- a/libweston/backend-drm/drm-kms-enums.h +++ b/libweston/backend-drm/drm-kms-enums.h @@ -57,6 +57,7 @@ enum wdrm_plane_property { WDRM_PLANE_ROTATION, WDRM_PLANE_ALPHA, WDRM_PLANE_COLOR_ENCODING, + WDRM_PLANE_COLOR_PIPELINE, WDRM_PLANE_COLOR_RANGE, WDRM_PLANE__COUNT }; @@ -95,6 +96,20 @@ enum wdrm_plane_color_encoding { }; #define WDRM_PLANE_COLOR_ENCODING_DEFAULT WDRM_PLANE_COLOR_ENCODING_BT709 +/** + * Possible values for the WDRM_PLANE_COLOR_PIPELINE property. + * + * This property is special: the enum values are not deterministic. Each enum + * value corresponds to the id of a colorop created at runtime by the KMS + * driver. drm_property_info_populate() expects well-known values for enum + * properties, so a dummy value is defined to allow it to populate plane->props + * correctly. + */ +enum wdrm_plane_color_pipeline { + WDRM_PLANE_COLOR_PIPELINE_DUMMY = 0, + WDRM_PLANE_COLOR_PIPELINE__COUNT +}; + /** * Possible values for the WDRM_PLANE_COLOR_RANGE property. */ @@ -105,6 +120,65 @@ enum wdrm_plane_color_range { }; #define WDRM_PLANE_COLOR_RANGE_DEFAULT WDRM_PLANE_COLOR_RANGE_LIMITED +/** + * List of properties attached to a DRM colorop. + */ +enum wdrm_colorop_property { + WDRM_COLOROP_TYPE = 0, + WDRM_COLOROP_NEXT, + WDRM_COLOROP_BYPASS, + WDRM_COLOROP_SIZE, + WDRM_COLOROP_DATA, + WDRM_COLOROP_MULTIPLIER, + WDRM_COLOROP_LUT1D_INTERPOLATION, + WDRM_COLOROP_LUT3D_INTERPOLATION, + WDRM_COLOROP_CURVE_1D, + WDRM_COLOROP__COUNT, +}; + +/** + * Possible values for the WDRM_COLOROP_TYPE property. + */ +enum wdrm_colorop_type { + WDRM_COLOROP_TYPE_1D_CURVE = 0, + WDRM_COLOROP_TYPE_1D_LUT, + WDRM_COLOROP_TYPE_CTM_3X4, + WDRM_COLOROP_TYPE_MULTIPLIER, + WDRM_COLOROP_TYPE_3D_LUT, + WDRM_COLOROP_TYPE__COUNT, +}; + +/** + * Possible values for the WDRM_COLOROP_CURVE_1D property. + */ +enum wdrm_colorop_curve_1d { + WDRM_COLOROP_CURVE_1D_SRGB_EOTF = 0, + WDRM_COLOROP_CURVE_1D_SRGB_INV_EOTF, + WDRM_COLOROP_CURVE_1D_PQ_125_EOTF, + WDRM_COLOROP_CURVE_1D_PQ_125_INV_EOTF, + WDRM_COLOROP_CURVE_1D_BT2020_INV_OETF, + WDRM_COLOROP_CURVE_1D_BT2020_OETF, + WDRM_COLOROP_CURVE_1D_GAMMA_22, + WDRM_COLOROP_CURVE_1D_GAMMA_22_INV, + WDRM_COLOROP_CURVE_1D__COUNT, +}; + +/** + * Possible values for the WDRM_COLOROP_LUT1D_INTERPOLATION property. + */ +enum wdrm_colorop_lut1d_interpolation { + WDRM_COLOROP_LUT1D_INTERPOLATION_LINEAR = 0, + WDRM_COLOROP_LUT1D_INTERPOLATION__COUNT, +}; + +/** + * Possible values for the WDRM_COLOROP_LUT3D_INTERPOLATION property. + */ +enum wdrm_colorop_lut3d_interpolation { + WDRM_COLOROP_LUT3D_INTERPOLATION_TETRAHEDRAL = 0, + WDRM_COLOROP_LUT3D_INTERPOLATION__COUNT, +}; + /** * List of properties attached to a DRM connector */ diff --git a/libweston/backend-drm/drm.c b/libweston/backend-drm/drm.c index 1d2bd3901..6994d4c29 100644 --- a/libweston/backend-drm/drm.c +++ b/libweston/backend-drm/drm.c @@ -51,6 +51,7 @@ #include #include #include +#include "colorops.h" #include "drm-internal.h" #include "shared/hash.h" #include "shared/helpers.h" @@ -1485,6 +1486,8 @@ drm_plane_create(struct drm_device *device, const drmModePlane *kplane) goto err; } + drm_plane_populate_color_pipelines(plane, props); + drmModeFreeObjectProperties(props); if (plane->type == WDRM_PLANE_TYPE__COUNT) @@ -1588,6 +1591,7 @@ drm_plane_destroy(struct drm_plane *plane) 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); drm_plane_state_free(plane->state_cur, true); drm_property_info_free(plane->props, WDRM_PLANE__COUNT); + drm_plane_release_color_pipelines(plane); weston_plane_release(&plane->base); weston_drm_format_array_fini(&plane->formats); wl_list_remove(&plane->link); @@ -2231,62 +2235,6 @@ drm_output_init_legacy_gamma_size(struct drm_output *output) return 0; } -static void -drm_colorop_3x1d_lut_destroy(struct drm_colorop_3x1d_lut *lut) -{ - wl_list_remove(&lut->destroy_listener.link); - wl_list_remove(&lut->link); - drmModeDestroyPropertyBlob(lut->device->kms_device->fd, lut->blob_id); - free(lut); -} - -static void -drm_colorop_3x1d_lut_destroy_handler(struct wl_listener *l, void *data) -{ - struct drm_colorop_3x1d_lut *lut; - - lut = wl_container_of(l, lut, destroy_listener); - assert(lut->xform == data); - - drm_colorop_3x1d_lut_destroy(lut); -} - -static struct drm_colorop_3x1d_lut * -drm_colorop_3x1d_lut_search(struct drm_device *device, - struct weston_color_transform *xform, - uint64_t lut_size) -{ - struct drm_colorop_3x1d_lut *colorop_lut; - - wl_list_for_each(colorop_lut, &device->drm_colorop_3x1d_lut_list, link) - if (colorop_lut->xform == xform && colorop_lut->lut_size == lut_size) - return colorop_lut; - - return NULL; -} - -static struct drm_colorop_3x1d_lut * -drm_colorop_3x1d_lut_create(struct weston_color_transform *xform, - struct drm_device *device, uint64_t lut_size, - uint32_t blob_id) -{ - struct drm_colorop_3x1d_lut *lut; - - lut = xzalloc(sizeof(*lut)); - - lut->device = device; - lut->blob_id = blob_id; - lut->xform = xform; - lut->lut_size = lut_size; - - wl_list_insert(&device->drm_colorop_3x1d_lut_list, &lut->link); - - lut->destroy_listener.notify = drm_colorop_3x1d_lut_destroy_handler; - wl_signal_add(&lut->xform->destroy_signal, &lut->destroy_listener); - - return lut; -} - static struct weston_vec3f * lut_3x1d_from_blend_to_output(struct weston_compositor *compositor, struct weston_color_transform *xform, @@ -2295,8 +2243,8 @@ lut_3x1d_from_blend_to_output(struct weston_compositor *compositor, /** * We expect steps to be valid for blend-to-output, as LittleCMS is * always able to optimize such xform. If that's invalid, we'd need to - * use to_shaper_plus_3dlut() to offload the xform, but the DRM API - * currently only supports us programming a LUT after blending. + * use to_clut() to offload the xform, but the DRM API currently only + * supports us programming a LUT after blending. */ if (!xform->steps_valid) { str_printf(err_msg, "xform color steps are invalid"); @@ -2333,15 +2281,12 @@ drm_output_pick_blend_to_output(struct drm_output *output) struct weston_compositor *compositor = output->base.compositor; struct drm_device *device = output->device; struct drm_backend *b = device->backend; - struct drm_colorop_3x1d_lut *colorop_lut; + struct drm_colorop_3x1d_lut_blob *colorop_lut; struct weston_color_transform *xform; - struct drm_color_lut *drm_lut; - size_t lut_size; - uint32_t gamma_lut_blob_id; + enum weston_color_curve_step curve_step; + size_t lut_len; struct weston_vec3f *cm_lut; char *err_msg; - unsigned int i; - int ret; /* Check if there's actually something to offload. */ weston_assert_ptr_not_null(compositor, output->base.color_outcome); @@ -2349,23 +2294,32 @@ drm_output_pick_blend_to_output(struct drm_output *output) if (!xform) return 0; - lut_size = output->crtc->lut_size; - if (lut_size == 0) { + lut_len = output->crtc->lut_size; + if (lut_len == 0) { drm_debug(b, "[output] can't offload blend-to-output: GAMMA_LUT_SIZE unsupported\n"); return -1; } /** - * First let's check if the xform has already been cached. If that's the + * For now we expect blend-to-output to be composed of pre-curve only, + * so lut_3x1d_from_blend_to_output() will return a LUT it creates from + * the xform pre-curve. + */ + curve_step = WESTON_COLOR_CURVE_STEP_PRE; + + /** + * First let's check if the LUT has already been cached. If that's the * case, we make use of it. */ - colorop_lut = drm_colorop_3x1d_lut_search(device, xform, lut_size); + colorop_lut = drm_colorop_3x1d_lut_blob_search(device, xform, curve_step, + DRM_COLOROP_3X1D_LUT_BLOB_QUANTIZATION_U16, + lut_len); if (colorop_lut) { output->blend_to_output_xform = colorop_lut; return 0; } - cm_lut = lut_3x1d_from_blend_to_output(compositor, xform, lut_size, &err_msg); + cm_lut = lut_3x1d_from_blend_to_output(compositor, xform, lut_len, &err_msg); if (!cm_lut) { drm_debug(b, "[output] failed to create 3x1D LUT for blend-to-output: %s\n", err_msg); @@ -2373,24 +2327,16 @@ drm_output_pick_blend_to_output(struct drm_output *output) return -1; } - drm_lut = xzalloc(lut_size * sizeof(*drm_lut)); - for (i = 0; i < lut_size; i++) { - drm_lut[i].red = cm_lut[i].r * 0xffff; - drm_lut[i].green = cm_lut[i].g * 0xffff; - drm_lut[i].blue = cm_lut[i].b * 0xffff; - } + output->blend_to_output_xform = + drm_colorop_3x1d_lut_blob_create(device, xform, curve_step, + DRM_COLOROP_3X1D_LUT_BLOB_QUANTIZATION_U16, + cm_lut, lut_len); free(cm_lut); - ret = drmModeCreatePropertyBlob(device->kms_device->fd, drm_lut, lut_size * sizeof(*drm_lut), - &gamma_lut_blob_id); - free(drm_lut); - if (ret < 0) { - drm_debug(b, "[output] failed to create blob for gamma LUT\n"); + if (!output->blend_to_output_xform) { + drm_debug(b, "[output] failed to create colorop 3x1D LUT"); return -1; } - output->blend_to_output_xform = - drm_colorop_3x1d_lut_create(xform, device, lut_size, - gamma_lut_blob_id); return 0; } @@ -4200,6 +4146,7 @@ drm_kms_device_destroy(struct drm_kms_device *kms_device) static void drm_device_destroy(struct drm_device *device) { + struct weston_compositor *ec = device->backend->compositor; struct drm_crtc *crtc, *crtc_tmp; struct drm_writeback *writeback, *writeback_tmp; @@ -4214,7 +4161,9 @@ drm_device_destroy(struct drm_device *device) &device->writeback_connector_list, link) drm_writeback_destroy(writeback); - weston_assert_true(ec, wl_list_empty(&device->drm_colorop_3x1d_lut_list)); + weston_assert_list_empty(ec, &device->drm_colorop_3x1d_lut_blob_list); + weston_assert_list_empty(ec, &device->drm_colorop_clut_blob_list); + weston_assert_list_empty(ec, &device->drm_colorop_matrix_blob_list); if (device->drm_event_source) wl_event_source_remove(device->drm_event_source); @@ -4554,7 +4503,9 @@ drm_device_create(struct drm_backend *backend, wl_list_init(&device->plane_list); create_planes(device); - wl_list_init(&device->drm_colorop_3x1d_lut_list); + wl_list_init(&device->drm_colorop_3x1d_lut_blob_list); + wl_list_init(&device->drm_colorop_clut_blob_list); + wl_list_init(&device->drm_colorop_matrix_blob_list); wl_list_init(&device->writeback_connector_list); if (drm_backend_discover_connectors(device, device->kms_device->udev_device, res) < 0) { diff --git a/libweston/backend-drm/kms.c b/libweston/backend-drm/kms.c index 073f797d8..cde357242 100644 --- a/libweston/backend-drm/kms.c +++ b/libweston/backend-drm/kms.c @@ -37,7 +37,10 @@ #include #include #include "shared/helpers.h" +#include "shared/string-helpers.h" +#include "shared/weston-assert.h" #include "shared/weston-drm-fourcc.h" +#include "colorops.h" #include "drm-internal.h" #include "pixel-formats.h" #include "presentation-time-server-protocol.h" @@ -91,6 +94,12 @@ struct drm_property_enum_info plane_color_encoding_enums[] = { }, }; +struct drm_property_enum_info plane_color_pipeline_enums[] = { + [WDRM_PLANE_COLOR_PIPELINE_DUMMY] = { + .name = "dummy", + }, +}; + struct drm_property_enum_info plane_color_range_enums[] = { [WDRM_PLANE_COLOR_RANGE_LIMITED] = { .name = "YCbCr limited range", @@ -131,6 +140,11 @@ const struct drm_property_info plane_props[] = { .enum_values = plane_color_encoding_enums, .num_enum_values = WDRM_PLANE_COLOR_ENCODING__COUNT, }, + [WDRM_PLANE_COLOR_PIPELINE] = { + .name = "COLOR_PIPELINE", + .enum_values = plane_color_pipeline_enums, + .num_enum_values = WDRM_PLANE_COLOR_PIPELINE__COUNT, + }, [WDRM_PLANE_COLOR_RANGE] = { .name = "COLOR_RANGE", .enum_values = plane_color_range_enums, @@ -138,6 +152,61 @@ const struct drm_property_info plane_props[] = { }, }; +static struct drm_property_enum_info colorop_type_enums[] = { + [WDRM_COLOROP_TYPE_1D_CURVE] = { .name = "1D Curve", }, + [WDRM_COLOROP_TYPE_1D_LUT] = { .name = "1D LUT", }, + [WDRM_COLOROP_TYPE_CTM_3X4] = { .name = "3x4 Matrix", }, + [WDRM_COLOROP_TYPE_MULTIPLIER] = { .name = "Multiplier", }, + [WDRM_COLOROP_TYPE_3D_LUT] = { .name = "3D LUT", }, +}; + +static struct drm_property_enum_info colorop_curve_1d_enums[] = { + [WDRM_COLOROP_CURVE_1D_SRGB_EOTF] = { .name = "sRGB EOTF", }, + [WDRM_COLOROP_CURVE_1D_SRGB_INV_EOTF] = { .name = "sRGB Inverse EOTF", }, + [WDRM_COLOROP_CURVE_1D_PQ_125_EOTF] = { .name = "PQ 125 EOTF", }, + [WDRM_COLOROP_CURVE_1D_PQ_125_INV_EOTF] = { .name = "PQ 125 Inverse EOTF", }, + [WDRM_COLOROP_CURVE_1D_BT2020_INV_OETF] = { .name = "BT.2020 Inverse OETF", }, + [WDRM_COLOROP_CURVE_1D_BT2020_OETF] = { .name = "BT.2020 OETF", }, + [WDRM_COLOROP_CURVE_1D_GAMMA_22] = { .name = "Gamma 2.2", }, + [WDRM_COLOROP_CURVE_1D_GAMMA_22_INV] = { .name = "Gamma 2.2 Inverse", }, +}; + +static struct drm_property_enum_info colorop_lut1d_interpolation_enums[] = { + [WDRM_COLOROP_LUT1D_INTERPOLATION_LINEAR] = { .name = "Linear", }, +}; + +static struct drm_property_enum_info colorop_lut3d_interpolation_enums[] = { + [WDRM_COLOROP_LUT3D_INTERPOLATION_TETRAHEDRAL] = { .name = "Tetrahedral", }, +}; + +const struct drm_property_info colorop_props[] = { + [WDRM_COLOROP_TYPE] = { + .name = "TYPE", + .enum_values = colorop_type_enums, + .num_enum_values = WDRM_COLOROP_TYPE__COUNT, + }, + [WDRM_COLOROP_NEXT] = { .name = "NEXT", }, + [WDRM_COLOROP_BYPASS] = { .name = "BYPASS", }, + [WDRM_COLOROP_SIZE] = { .name = "SIZE", }, + [WDRM_COLOROP_DATA] = { .name = "DATA", }, + [WDRM_COLOROP_MULTIPLIER] = { .name = "MULTIPLIER", }, + [WDRM_COLOROP_CURVE_1D] = { + .name = "CURVE_1D_TYPE", + .enum_values = colorop_curve_1d_enums, + .num_enum_values = WDRM_COLOROP_CURVE_1D__COUNT, + }, + [WDRM_COLOROP_LUT1D_INTERPOLATION] = { + .name = "LUT1D_INTERPOLATION", + .enum_values = colorop_lut1d_interpolation_enums, + .num_enum_values = WDRM_COLOROP_LUT1D_INTERPOLATION__COUNT, + }, + [WDRM_COLOROP_LUT3D_INTERPOLATION] = { + .name = "LUT3D_INTERPOLATION", + .enum_values = colorop_lut3d_interpolation_enums, + .num_enum_values = WDRM_COLOROP_LUT3D_INTERPOLATION__COUNT, + }, +}; + struct drm_property_enum_info dpms_state_enums[] = { [WDRM_DPMS_STATE_OFF] = { .name = "Off", @@ -1093,6 +1162,28 @@ plane_add_prop(drmModeAtomicReq *req, struct drm_plane *plane, return (ret <= 0) ? -1 : 0; } +static int +colorop_add_prop(drmModeAtomicReq *req, struct drm_colorop *colorop, + enum wdrm_colorop_property prop, uint64_t val) +{ + struct drm_plane *plane = colorop->pipeline->plane; + struct drm_device *device = plane->device; + struct drm_backend *b = device->backend; + struct drm_property_info *info = &colorop->props[prop]; + int ret; + + drm_debug(b, "\t\t\t[COLOROP:%lu] %lu (%s) -> %llu (0x%llx)\n", + (unsigned long) colorop->id, + (unsigned long) info->prop_id, info->name, + (unsigned long long) val, (unsigned long long) val); + + if (info->prop_id == 0) + return -1; + + ret = drmModeAtomicAddProperty(req, colorop->id, info->prop_id, val); + return (ret <= 0) ? -1 : 0; +} + static bool drm_connector_has_prop(struct drm_connector *connector, enum wdrm_connector_property prop) @@ -1294,6 +1385,168 @@ drm_plane_set_color_range(struct drm_plane *plane, return plane_add_prop(req, plane, WDRM_PLANE_COLOR_RANGE, color_range); } +static bool +colorop_program(drmModeAtomicReq *req, struct drm_colorop *colorop, + enum wdrm_colorop_property colorop_prop, + uint64_t prop_val, char **err_msg) +{ + int ret; + + if (colorop->can_bypass) { + ret = colorop_add_prop(req, colorop, WDRM_COLOROP_BYPASS, 0); + if (ret < 0) { + str_printf(err_msg, "failed to set colorop id %u bypass == false", + colorop->id); + return false; + } + } + + ret = colorop_add_prop(req, colorop, colorop_prop, prop_val); + if (ret < 0) { + str_printf(err_msg, "failed to program colorop id %u type %s", + colorop->id, drm_colorop_type_to_str(colorop)); + return false; + } + + return true; +} + +static bool +set_interp(drmModeAtomicReq *req, struct drm_colorop *colorop, + enum wdrm_colorop_property interp_prop, uint64_t interp_val) +{ + struct drm_property_info *info = &colorop->props[interp_prop]; + int ret; + + if (info->enum_values[interp_val].valid) { + ret = colorop_add_prop(req, colorop, interp_prop, interp_val); + if (ret >= 0) + return true; + } + + return false; +} + +static bool +drm_colorop_program(drmModeAtomicReq *req, struct drm_colorop_state *colorop_state, + const char *indent, char **err_msg) +{ + struct drm_colorop *colorop = colorop_state->colorop; + struct drm_color_pipeline *pipeline = colorop->pipeline; + struct drm_backend *b = pipeline->plane->device->backend; + struct weston_compositor *compositor = pipeline->plane->base.compositor; + enum wdrm_colorop_property colorop_prop; + uint64_t prop_val; + + switch (colorop_state->object.type) { + case COLOROP_OBJECT_TYPE_CURVE: + colorop_prop = WDRM_COLOROP_CURVE_1D; + prop_val = colorop_state->object.curve_type_prop_val; + return colorop_program(req, colorop, colorop_prop, prop_val, err_msg); + case COLOROP_OBJECT_TYPE_MATRIX: + colorop_prop = WDRM_COLOROP_DATA; + prop_val = colorop_state->object.matrix_blob_id; + return colorop_program(req, colorop, colorop_prop, prop_val, err_msg); + case COLOROP_OBJECT_TYPE_3x1D_LUT: + if (!set_interp(req, colorop, WDRM_COLOROP_LUT1D_INTERPOLATION, + WDRM_COLOROP_LUT1D_INTERPOLATION_LINEAR)) + drm_debug(b, "%s[colorop] linear LUT1D interpolation not supported or failed to set;\n" + "%susing current value set on driver\n", indent, indent); + colorop_prop = WDRM_COLOROP_DATA; + prop_val = colorop_state->object.lut_3x1d_blob_id; + return colorop_program(req, colorop, colorop_prop, prop_val, err_msg); + case COLOROP_OBJECT_TYPE_3D_LUT: + if (!set_interp(req, colorop, WDRM_COLOROP_LUT3D_INTERPOLATION, + WDRM_COLOROP_LUT3D_INTERPOLATION_TETRAHEDRAL)) + drm_debug(b, "%s[colorop] tetrahedral LUT3D interpolation not supported or failed to set;\n" + "%susing current value set on driver\n", indent, indent); + colorop_prop = WDRM_COLOROP_DATA; + prop_val = colorop_state->object.lut_3d_blob_id; + return colorop_program(req, colorop, colorop_prop, prop_val, err_msg); + case COLOROP_OBJECT_TYPE_MULTIPLIER: + colorop_prop = WDRM_COLOROP_MULTIPLIER; + prop_val = colorop_state->object.multiplier; + return colorop_program(req, colorop, colorop_prop, prop_val, err_msg); + } + weston_assert_not_reached(compositor, + "unknown drm_colorop_state object type"); +} + +static struct drm_colorop_state * +drm_colorop_state_iter(struct drm_color_pipeline_state *pipeline_state, + struct drm_colorop_state *iter) +{ + struct wl_list *list = &pipeline_state->colorop_state_list; + struct wl_list *node; + + if (iter) + node = iter->link.next; + else + node = list->next; + + if (node == list) + return NULL; + + return container_of(node, struct drm_colorop_state, link); +} + +static int +drm_color_pipeline_program(drmModeAtomicReq *req, + struct drm_color_pipeline_state *pipeline_state, + const char *indent) +{ + struct drm_color_pipeline *pipeline = pipeline_state->pipeline; + struct drm_plane *plane = pipeline->plane; + struct drm_backend *b = plane->device->backend; + struct drm_colorop_state *colorop_state; + struct drm_colorop *colorop; + char *err_msg; + int ret_drm; + bool ret; + + drm_debug(b, "%s[PLANE:%lu] %lu (%s) -> %llu (0x%llx)\n", + indent, (unsigned long) plane->plane_id, + (unsigned long) plane->pipeline_props_id, "COLOR_PIPELINE", + (unsigned long long) pipeline_state->pipeline->id, + (unsigned long long) pipeline_state->pipeline->id); + + colorop_state = drm_colorop_state_iter(pipeline_state, + NULL /* previous colorop state (none) */); + wl_list_for_each(colorop, &pipeline->colorop_list, link) { + /* If a colorop is not in the colorop state list, bypass it. */ + if (!colorop_state || colorop != colorop_state->colorop) { + weston_assert_true(b->compositor, colorop->can_bypass); + + ret_drm = colorop_add_prop(req, colorop, WDRM_COLOROP_BYPASS, 1); + if (ret_drm >= 0) + continue; + + drm_debug(b, "%s%s[colorop] failed to set colorop id %u bypass == true", + indent, indent, colorop->id); + goto err; + } + + ret = drm_colorop_program(req, colorop_state, indent, &err_msg); + if (!ret) { + drm_debug(b, "%s%s[colorop] %s\n", indent, indent, err_msg); + free(err_msg); + goto err; + } + + colorop_state = drm_colorop_state_iter(pipeline_state, colorop_state); + } + weston_assert_ptr_null(b->compositor, colorop_state); + + /* Set plane pipeline. */ + drmModeAtomicAddProperty(req, plane->plane_id, + plane->pipeline_props_id, pipeline->id); + return 0; + +err: + drm_debug(b, "%s%s[colorop] failed to program pipeline\n", indent, indent); + return -1; +} + static int drm_output_apply_state_atomic(struct drm_output_state *state, drmModeAtomicReq *req, @@ -1455,6 +1708,16 @@ drm_output_apply_state_atomic(struct drm_output_state *state, ret |= plane_add_prop(req, plane, WDRM_PLANE_FB_DAMAGE_CLIPS, plane_state->damage_blob_id); + if (plane->props[WDRM_PLANE_COLOR_PIPELINE].prop_id != 0) { + if (plane_state->pipeline_state) { + ret |= drm_color_pipeline_program(req, plane_state->pipeline_state, + "\t\t\t"); + } else { + ret |= plane_add_prop(req, plane, + WDRM_PLANE_COLOR_PIPELINE, 0); + } + } + if (plane_state->fb && plane_state->fb->format) pinfo = plane_state->fb->format; @@ -2079,6 +2342,13 @@ init_kms_caps(struct drm_device *device) drmSetClientCap(device->kms_device->fd, DRM_CLIENT_CAP_WRITEBACK_CONNECTORS, 1); +#ifdef DRM_CLIENT_CAP_PLANE_COLOR_PIPELINE + ret = drmSetClientCap(device->kms_device->fd, DRM_CLIENT_CAP_PLANE_COLOR_PIPELINE, 1); + device->color_pipeline_supported = (ret == 0); +#else + device->color_pipeline_supported = false; +#endif + ret = drmGetCap(device->kms_device->fd, DRM_CAP_ATOMIC_ASYNC_PAGE_FLIP, &cap); if (ret == 0) device->tearing_supported = cap; diff --git a/libweston/backend-drm/meson.build b/libweston/backend-drm/meson.build index 5a5320420..210c40572 100644 --- a/libweston/backend-drm/meson.build +++ b/libweston/backend-drm/meson.build @@ -32,6 +32,10 @@ srcs_drm = [ presentation_time_server_protocol_h, ] +if (libdrm_supports_color_pipeline) + srcs_drm += 'colorops.c' +endif + deps_drm = [ dep_egl, # optional dep_vulkan, # optional diff --git a/libweston/backend-drm/state-helpers.c b/libweston/backend-drm/state-helpers.c index dbcef7696..dd8743246 100644 --- a/libweston/backend-drm/state-helpers.c +++ b/libweston/backend-drm/state-helpers.c @@ -32,6 +32,7 @@ #include #include +#include "colorops.h" #include "drm-internal.h" #include "shared/weston-assert.h" #include "shared/weston-drm-fourcc.h" @@ -95,6 +96,8 @@ drm_plane_state_free(struct drm_plane_state *state, bool force) state->in_fence_fd = -1; state->zpos = DRM_PLANE_ZPOS_INVALID_PLANE; state->alpha = DRM_PLANE_ALPHA_OPAQUE; + drm_color_pipeline_state_destroy(state->pipeline_state); + state->pipeline_state = NULL; /* Once the damage blob has been submitted, it is refcounted internally * by the kernel, which means we can safely discard it. diff --git a/libweston/backend-drm/state-propose.c b/libweston/backend-drm/state-propose.c index 9e4c1f19b..0babeb4f9 100644 --- a/libweston/backend-drm/state-propose.c +++ b/libweston/backend-drm/state-propose.c @@ -39,6 +39,7 @@ #include "drm-internal.h" #include "color.h" +#include "colorops.h" #include "color-representation.h" #include "linux-dmabuf.h" #include "presentation-time-server-protocol.h" @@ -141,6 +142,21 @@ drm_output_try_paint_node_on_plane(struct drm_plane_handle *handle, state->fb = drm_fb_ref(fb); state->in_fence_fd = surface->acquire_fence_fd; + drm_color_pipeline_state_destroy(state->pipeline_state); + state->pipeline_state = NULL; + if (pnode->surf_xform.transform) { + state->pipeline_state = + drm_color_pipeline_state_from_xform(plane, + pnode->surf_xform.transform, + "\t\t\t\t"); + if (!state->pipeline_state) { + drm_debug(b, "\t\t\t\t[view] not placing paint node %s on plane %lu: " + "not compatible with surface color xform\n", + pnode->internal_name, (unsigned long) plane->plane_id); + goto out; + } + } + if (fb->format && fb->format->color_model == COLOR_MODEL_YUV) { struct weston_color_representation color_rep; const struct weston_color_matrix_coef_info *matrix_coef_info; @@ -584,6 +600,14 @@ pnode_can_use_plane(struct drm_output_state *output_state, return false; } + /* If we have a surf color xform we need to be able to offload that to KMS. */ + if (pnode->surf_xform.transform && plane->num_color_pipelines == 0) { + drm_debug(b, "\t\t\t\t[plane] not trying plane %d: surf_xform present " + "but plane has no color pipelines\n", + plane->plane_id); + return false; + } + /* If the surface buffer has an in-fence fd, but the plane doesn't * support fences, we can't place the buffer on this plane. */ if (pnode->surface->acquire_fence_fd >= 0 && @@ -1371,8 +1395,8 @@ drm_output_propose_state(struct weston_output *output_base, pnode->try_view_on_plane_failure_reasons |= FAILURE_REASONS_OUTPUT_COLOR_EFFECT; - if (pnode->surf_xform.transform != NULL || - !pnode->surf_xform.identity_pipeline) + if (pnode->surf_xform.transform && (!device->color_pipeline_supported || + !pnode->output->from_blend_to_output_by_backend)) pnode->try_view_on_plane_failure_reasons |= FAILURE_REASONS_NO_COLOR_TRANSFORM; diff --git a/libweston/color-lcms/color-transform.c b/libweston/color-lcms/color-transform.c index d3c20c048..972dbd0f4 100644 --- a/libweston/color-lcms/color-transform.c +++ b/libweston/color-lcms/color-transform.c @@ -417,8 +417,7 @@ lcms_curve_matches_any_tf(struct weston_compositor *compositor, const float lcms_curve_params[3][MAX_PARAMS_LCMS_PARAM_CURVE]) { struct weston_color_curve_parametric curve = { 0 }; - unsigned int i, j; - uint32_t n_lcms_curve_params; + unsigned int i; curve.clamped_input = clamped_input; @@ -428,28 +427,34 @@ lcms_curve_matches_any_tf(struct weston_compositor *compositor, * LittleCMS type 1 is the pure power-law curve, which is a * special case of LINPOW. See init_curve_from_type_1(). */ - n_lcms_curve_params = 1; curve.type = WESTON_COLOR_CURVE_PARAMETRIC_TYPE_LINPOW; + for (i = 0; i < 3; i++) { + curve.params.chan[i].g = lcms_curve_params[i][0]; + /* a = 1, b = 0, c = 1, d = 0 */ + curve.params.chan[i].a = 1.0f; + curve.params.chan[i].b = 0.0f; + curve.params.chan[i].c = 1.0f; + curve.params.chan[i].d = 0.0f; + } break; case 4: /** * LittleCMS type 4 is almost exactly the same as LINPOW. See * init_curve_from_type_4(). */ - n_lcms_curve_params = 5; curve.type = WESTON_COLOR_CURVE_PARAMETRIC_TYPE_LINPOW; + for (i = 0; i < 3; i++) { + curve.params.chan[i].g = lcms_curve_params[i][0]; + curve.params.chan[i].a = lcms_curve_params[i][1]; + curve.params.chan[i].b = lcms_curve_params[i][2]; + curve.params.chan[i].c = lcms_curve_params[i][3]; + curve.params.chan[i].d = lcms_curve_params[i][4]; + } break; default: return NULL; } - weston_assert_u32_le(compositor, - n_lcms_curve_params, MAX_PARAMS_LCMS_PARAM_CURVE); - - for (i = 0; i < 3; i++) - for (j = 0; j < n_lcms_curve_params; j++) - curve.params.chan[i].data[j] = lcms_curve_params[i][j]; - return weston_color_tf_info_from_parametric_curve(&curve); } @@ -2058,10 +2063,10 @@ cmlcms_color_transform_recipe_string(const struct cmlcms_color_transform_recipe } static bool -build_3d_lut(struct weston_compositor *compositor, - const struct cmlcms_color_transformer *transformer, - unsigned int len_shaper, const float *shaper, - unsigned int len_lut3d, float *lut3d) +build_clut(struct weston_compositor *compositor, + const struct cmlcms_color_transformer *transformer, + unsigned int len_shaper, const float *shaper, + unsigned int len_clut, float *clut) { const float *const red_curve = &shaper[0]; const float *const green_curve = &shaper[len_shaper]; @@ -2077,27 +2082,26 @@ build_3d_lut(struct weston_compositor *compositor, * Ensure the indices and byte counts cannot overflow, * and memory usage does not get ridiculous. Arbitrary limit. */ - weston_assert_u32_lt(compositor, len_lut3d, 100); + weston_assert_u32_lt(compositor, len_clut, 100); /* - * A temporary allocation that holds two 1D LUTs of length len_lut3d - * and one scratch array of vec3f of length len_lut3d. + * A temporary allocation that holds two 1D LUTs of length len_clut + * and one scratch array of vec3f of length len_clut. */ const uint32_t bytes_per_elem = 2 * sizeof (float) + sizeof *rgb_in; - tmp = malloc(len_lut3d * bytes_per_elem); + tmp = malloc(len_clut * bytes_per_elem); inverse_r = &tmp[0]; - inverse_g = &tmp[len_lut3d]; - rgb_in = (struct weston_vec3f *)&tmp[2 * len_lut3d]; - + inverse_g = &tmp[len_clut]; + rgb_in = (struct weston_vec3f *)&tmp[2 * len_clut]; /* * For each channel, use the shaper to compute the value x such that * y(x) = index / (len - 1). As the shaper is a LUT, we find the closest * neighbors of such point (x, y) and then use linear interpolation to * estimate x. */ - for (i = 0; i < len_lut3d; i++) { - float y = (float)i / (len_lut3d - 1); + for (i = 0; i < len_clut; i++) { + float y = (float)i / (len_clut - 1); inverse_r[i] = weston_inverse_evaluate_lut1d(compositor, len_shaper, red_curve, @@ -2109,9 +2113,9 @@ build_3d_lut(struct weston_compositor *compositor, } /* - * Fill in the 3D LUT: LUT(Rin, Gin, Bin) = { Rout, Gout, Bout } + * Fill in the 3D cLUT: LUT(Rin, Gin, Bin) = { Rout, Gout, Bout } * Each of Rin, Gin and Bin varies from 0.0 to 1.0. The range [0.0, 1.0] - * is evenly divided into len_lut3d number of sampling points. The + * is evenly divided into len_clut number of sampling points. The * indices of the sampling points are index_r, index_g, index_b. * * To compute { Rout, Gout, Bout }, first Rin, Gin, Bin must go through @@ -2121,29 +2125,29 @@ build_3d_lut(struct weston_compositor *compositor, * separable. * * The next step is not separable, so we iterate through all points in - * the 3D volume. The points are transformed len_lut3d points at a time + * the 3D volume. The points are transformed len_clut points at a time * (rgb_in array) to strike a balance between the number of function * calls and the memory requirements. */ - for (index_b = 0; index_b < len_lut3d; index_b++) { + for (index_b = 0; index_b < len_clut; index_b++) { float inverse_b = weston_inverse_evaluate_lut1d(compositor, len_shaper, blue_curve, - (float)index_b / (len_lut3d - 1)); - for (i = 0; i < len_lut3d; i++) + (float)index_b / (len_clut - 1)); + for (i = 0; i < len_clut; i++) rgb_in[i].b = inverse_b; - for (index_g = 0; index_g < len_lut3d; index_g++) { - for (index_r = 0; index_r < len_lut3d; index_r++) { + for (index_g = 0; index_g < len_clut; index_g++) { + for (index_r = 0; index_r < len_clut; index_r++) { rgb_in[index_r].g = inverse_g[index_g]; rgb_in[index_r].r = inverse_r[index_r]; } index_r = 0; - i = 3 * (index_r + len_lut3d * (index_g + len_lut3d * index_b)); + i = 3 * (index_r + len_clut * (index_g + len_clut * index_b)); cmlcms_color_transformer_eval(compositor, transformer, - (struct weston_vec3f *)&lut3d[i], - rgb_in, len_lut3d); + (struct weston_vec3f *)&clut[i], + rgb_in, len_clut); } } @@ -2256,16 +2260,16 @@ out: /** * Based on [1]. We get the transformer and decompose into a shaper - * (3x1D LUT) + 3D LUT. With that, we can reduce the 3D LUT dimension size + * (3x1D LUT) + 3D cLUT. With that, we can reduce the 3D LUT dimension size * without losing precision. 3D LUT dimension size is problematic because it * demands n³ memory. * * [1] https://www.littlecms.com/ASICprelinerization_CGIV08.pdf */ static bool -xform_to_shaper_plus_3dlut(struct weston_color_transform *xform_base, - uint32_t len_shaper, float *shaper, - uint32_t len_lut3d, float *lut3d) +xform_to_clut(struct weston_color_transform *xform_base, + uint32_t len_shaper, float *shaper, + uint32_t len_clut, float *clut) { struct cmlcms_color_transform *xform = to_cmlcms_xform(xform_base); struct weston_compositor *compositor = xform_base->cm->compositor; @@ -2276,8 +2280,8 @@ xform_to_shaper_plus_3dlut(struct weston_color_transform *xform_base, if (!ret) return false; - ret = build_3d_lut(compositor, &xform->transformer, - len_shaper, shaper, len_lut3d, lut3d); + ret = build_clut(compositor, &xform->transformer, + len_shaper, shaper, len_clut, clut); if (!ret) return false; @@ -2306,7 +2310,7 @@ cmlcms_color_transform_create(struct weston_color_manager_lcms *cm, xform = xzalloc(sizeof *xform); weston_color_transform_init(&xform->base, &cm->base); wl_list_init(&xform->link); - xform->base.to_shaper_plus_3dlut = xform_to_shaper_plus_3dlut; + xform->base.to_clut = xform_to_clut; cmlcms_color_transform_recipe_copy(&xform->search_key, recipe); weston_log_scope_printf(cm->transforms_scope, diff --git a/libweston/color-properties.c b/libweston/color-properties.c index 1d39c54fc..6e9913887 100644 --- a/libweston/color-properties.c +++ b/libweston/color-properties.c @@ -279,6 +279,8 @@ static const struct weston_color_tf_info color_tf_info_table[] = { .tf = WESTON_TF_BT1886, .desc = "BT.1886", .protocol_tf = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_BT1886, + .kms_colorop = WDRM_COLOROP_CURVE_1D__COUNT, + .kms_colorop_inverse = WDRM_COLOROP_CURVE_1D__COUNT, .count_parameters = 0, /** * NOTE: This is the BT.1886 special case of L_B = 0 and @@ -292,6 +294,8 @@ static const struct weston_color_tf_info color_tf_info_table[] = { .tf = WESTON_TF_GAMMA22, .desc = "assumed display gamma 2.2", .protocol_tf = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA22, + .kms_colorop = WDRM_COLOROP_CURVE_1D_GAMMA_22, + .kms_colorop_inverse = WDRM_COLOROP_CURVE_1D_GAMMA_22_INV, .count_parameters = 0, .curve_params_valid = true, .curve = POWER_LAW(2.2, true), @@ -301,6 +305,8 @@ static const struct weston_color_tf_info color_tf_info_table[] = { .tf = WESTON_TF_GAMMA28, .desc = "assumed display gamma 2.8", .protocol_tf = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA28, + .kms_colorop = WDRM_COLOROP_CURVE_1D__COUNT, + .kms_colorop_inverse = WDRM_COLOROP_CURVE_1D__COUNT, .count_parameters = 0, .curve_params_valid = true, .curve = POWER_LAW(2.8, true), @@ -310,12 +316,16 @@ static const struct weston_color_tf_info color_tf_info_table[] = { .tf = WESTON_TF_EXT_LINEAR, .desc = "extended linear", .protocol_tf = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR, + .kms_colorop = WDRM_COLOROP_CURVE_1D__COUNT, + .kms_colorop_inverse = WDRM_COLOROP_CURVE_1D__COUNT, .count_parameters = 0, }, { .tf = WESTON_TF_SRGB, .desc = "sRGB piece-wise", .protocol_tf = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_SRGB, + .kms_colorop = WDRM_COLOROP_CURVE_1D_SRGB_EOTF, + .kms_colorop_inverse = WDRM_COLOROP_CURVE_1D_SRGB_INV_EOTF, .count_parameters = 0, .curve_params_valid = true, .curve = SRGB_PIECE_WISE(true), @@ -325,6 +335,8 @@ static const struct weston_color_tf_info color_tf_info_table[] = { .tf = WESTON_TF_EXT_SRGB, .desc = "Extended sRGB piece-wise", .protocol_tf = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_SRGB, + .kms_colorop = WDRM_COLOROP_CURVE_1D__COUNT, + .kms_colorop_inverse = WDRM_COLOROP_CURVE_1D__COUNT, .count_parameters = 0, .curve_params_valid = true, .curve = SRGB_PIECE_WISE(false), @@ -334,47 +346,63 @@ static const struct weston_color_tf_info color_tf_info_table[] = { .tf = WESTON_TF_ST240, .desc = "SMPTE ST 240", .protocol_tf = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST240, + .kms_colorop = WDRM_COLOROP_CURVE_1D__COUNT, + .kms_colorop_inverse = WDRM_COLOROP_CURVE_1D__COUNT, .count_parameters = 0, }, { .tf = WESTON_TF_ST428, .desc = "SMPTE ST 428", .protocol_tf = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST428, + .kms_colorop = WDRM_COLOROP_CURVE_1D__COUNT, + .kms_colorop_inverse = WDRM_COLOROP_CURVE_1D__COUNT, .count_parameters = 0, }, { .tf = WESTON_TF_ST2084_PQ, .desc = "Perceptual Quantizer", .protocol_tf = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST2084_PQ, + .kms_colorop = WDRM_COLOROP_CURVE_1D__COUNT, + .kms_colorop_inverse = WDRM_COLOROP_CURVE_1D__COUNT, .count_parameters = 0, }, { .tf = WESTON_TF_LOG_100, .desc = "logarithmic 100:1", .protocol_tf = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_LOG_100, + .kms_colorop = WDRM_COLOROP_CURVE_1D__COUNT, + .kms_colorop_inverse = WDRM_COLOROP_CURVE_1D__COUNT, .count_parameters = 0, }, { .tf = WESTON_TF_LOG_316, .desc = "logarithmic (100*Sqrt(10) : 1)", .protocol_tf = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_LOG_316, + .kms_colorop = WDRM_COLOROP_CURVE_1D__COUNT, + .kms_colorop_inverse = WDRM_COLOROP_CURVE_1D__COUNT, .count_parameters = 0, }, { .tf = WESTON_TF_XVYCC, .desc = "IEC 61966-2-4 (xvYCC)", .protocol_tf = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_XVYCC, + .kms_colorop = WDRM_COLOROP_CURVE_1D__COUNT, + .kms_colorop_inverse = WDRM_COLOROP_CURVE_1D__COUNT, .count_parameters = 0, }, { .tf = WESTON_TF_HLG, .desc = "Hybrid log-gamma", .protocol_tf = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_HLG, + .kms_colorop = WDRM_COLOROP_CURVE_1D__COUNT, + .kms_colorop_inverse = WDRM_COLOROP_CURVE_1D__COUNT, .count_parameters = 0, }, { .tf = WESTON_TF_POWER, .desc = "power-law with custom exponent", + .kms_colorop = WDRM_COLOROP_CURVE_1D__COUNT, + .kms_colorop_inverse = WDRM_COLOROP_CURVE_1D__COUNT, .count_parameters = 1, }, }; diff --git a/libweston/color-properties.h b/libweston/color-properties.h index 9246446a5..7698242bb 100644 --- a/libweston/color-properties.h +++ b/libweston/color-properties.h @@ -110,6 +110,10 @@ struct weston_color_tf_info { /** CM&HDR protocol extension value representing the tf. */ uint32_t protocol_tf; + /** KMS 1D curve colorop value representing the tf. */ + uint32_t kms_colorop; + uint32_t kms_colorop_inverse; + /* The protocol also has support for parameterized functions, i.e. * certain known functions that clients can define passing arbitrary * parameters. */ diff --git a/libweston/color.h b/libweston/color.h index e2afcfc47..7957119ab 100644 --- a/libweston/color.h +++ b/libweston/color.h @@ -383,8 +383,8 @@ struct weston_color_transform { /** * When this is true, users are allowed to use the steps described below * (pre curve, color mapping and post curve) and implement the color - * transformation themselves. Otherwise this is forbidden and - * to_shaper_plus_3dlut() must be used. + * transformation themselves. Otherwise this is forbidden and to_clut() + * must be used. */ bool steps_valid; @@ -403,23 +403,23 @@ struct weston_color_transform { struct weston_color_curve post_curve; /** - * Decompose the color transformation into a shaper (3x1D LUT) and a 3D - * LUT. + * Decompose the color transformation into a shaper (3x1D LUT) followed + * by a 3D cLUT. * * \param xform_base The color transformation to decompose. * \param len_shaper Number of taps in each of the 1D LUT. * \param shaper Where the shaper is saved, caller's responsibility to * allocate. - * \param len_lut3d The 3D LUT's length for each dimension. - * \param lut3d Where the 3D LUT is saved, caller's responsibility to - * allocate. Its layout on memory is: lut3d[B][G][R], i.e. R is the + * \param len_clut The 3D cLUT's length for each dimension. + * \param clut Where the 3D cLUT is saved, caller's responsibility to + * allocate. Its layout on memory is: clut[B][G][R], i.e. R is the * innermost and its index grow faster, followed by G and then B. * \return True on success, false otherwise. */ bool - (*to_shaper_plus_3dlut)(struct weston_color_transform *xform_base, - uint32_t len_shaper, float *shaper, - uint32_t len_lut3d, float *lut3d); + (*to_clut)(struct weston_color_transform *xform_base, + uint32_t len_shaper, float *shaper, + uint32_t len_clut, float *clut); }; struct weston_cvd_correction { diff --git a/libweston/linux-dmabuf.c b/libweston/linux-dmabuf.c index c460ff58e..75b2944d0 100644 --- a/libweston/linux-dmabuf.c +++ b/libweston/linux-dmabuf.c @@ -800,7 +800,7 @@ weston_dmabuf_feedback_send_all(struct weston_compositor *compositor, { struct wl_resource *res; - weston_assert_true(compositor, !wl_list_empty(&dmabuf_feedback->resource_list)); + weston_assert_list_not_empty(compositor, &dmabuf_feedback->resource_list); wl_resource_for_each(res, &dmabuf_feedback->resource_list) weston_dmabuf_feedback_send(dmabuf_feedback, format_table, res, false); diff --git a/libweston/meson.build b/libweston/meson.build index b2a8cac8d..9fc9006ad 100644 --- a/libweston/meson.build +++ b/libweston/meson.build @@ -208,6 +208,10 @@ if get_option('backend-drm') dep_session_helper = declare_dependency(link_with: lib_session_helper) endif +libdrm_version = dep_libdrm.version() +libdrm_supports_color_pipeline = libdrm_version.version_compare('>=2.4.130') +config_h.set10('CAN_OFFLOAD_COLOR_PIPELINE', libdrm_supports_color_pipeline) + lib_libinput_backend = static_library( 'libinput-backend', [ diff --git a/libweston/renderer-gl/gl-shader-config-color-transformation.c b/libweston/renderer-gl/gl-shader-config-color-transformation.c index b225df00a..2c051980a 100644 --- a/libweston/renderer-gl/gl-shader-config-color-transformation.c +++ b/libweston/renderer-gl/gl-shader-config-color-transformation.c @@ -376,9 +376,9 @@ gl_renderer_color_transform_create_3dlut(struct gl_renderer *gr, { struct gl_renderer_color_transform *gl_xform = NULL; float *shaper = NULL; - float *lut3d = NULL; - float len_shaper; - float len_lut3d; + float *clut = NULL; + uint32_t len_shaper; + uint32_t len_clut; bool ok; /** @@ -386,22 +386,21 @@ gl_renderer_color_transform_create_3dlut(struct gl_renderer *gr, * excessive memory consumption. */ len_shaper = 1024; - len_lut3d = 33; + len_clut = 33; shaper = zalloc(len_shaper * 3 * sizeof(*shaper)); if (!shaper) goto err; - lut3d = zalloc(3 * len_lut3d * len_lut3d * len_lut3d * sizeof(*lut3d)); - if (!lut3d) + clut = zalloc(3 * len_clut * len_clut * len_clut * sizeof(*clut)); + if (!clut) goto err; gl_xform = gl_renderer_color_transform_create(xform); if (!gl_xform) goto err; - ok = xform->to_shaper_plus_3dlut(xform, len_shaper, shaper, - len_lut3d, lut3d); + ok = xform->to_clut(xform, len_shaper, shaper, len_clut, clut); if (!ok) goto err; @@ -411,18 +410,18 @@ gl_renderer_color_transform_create_3dlut(struct gl_renderer *gr, goto err; gl_color_mapping_lut_3d_init(gr, &gl_xform->mapping, - len_lut3d, lut3d); + len_clut, clut); free(shaper); - free(lut3d); + free(clut); return gl_xform; err: if (shaper) free(shaper); - if (lut3d) - free(lut3d); + if (clut) + free(clut); if (gl_xform) gl_renderer_color_transform_destroy(gl_xform); return NULL; diff --git a/shared/weston-assert.h b/shared/weston-assert.h index f72752acc..75cb6d66b 100644 --- a/shared/weston-assert.h +++ b/shared/weston-assert.h @@ -32,6 +32,8 @@ #include #include +#include + struct weston_compositor; __attribute__((noreturn, format(printf, 2, 3))) @@ -53,24 +55,24 @@ weston_assert_fail_(const struct weston_compositor *compositor, const char *fmt, #define weston_assert_(compositor, a, b, val_type, val_fmt, cmp) \ ({ \ - struct weston_compositor *ec = compositor; \ + struct weston_compositor *wc_ = compositor; \ val_type a_ = (a); \ val_type b_ = (b); \ bool cond = a_ cmp b_; \ if (!cond) \ - custom_assert_fail_(ec, "%s:%u: Assertion %s %s %s (" val_fmt " %s " val_fmt ") failed!\n", \ + custom_assert_fail_(wc_, "%s:%u: Assertion %s %s %s (" val_fmt " %s " val_fmt ") failed!\n", \ __FILE__, __LINE__, #a, #cmp, #b, a_, #cmp, b_); \ cond; \ }) #define weston_assert_fn_(compositor, fn, a, b, val_type, val_fmt, cmp) \ ({ \ - struct weston_compositor *ec = compositor; \ + struct weston_compositor *wc_ = compositor; \ val_type a_ = (a); \ val_type b_ = (b); \ bool cond = fn(a_, b_) cmp 0; \ if (!cond) \ - custom_assert_fail_(ec, "%s:%u: Assertion %s %s %s (" val_fmt " %s " val_fmt ") failed!\n", \ + custom_assert_fail_(wc_, "%s:%u: Assertion %s %s %s (" val_fmt " %s " val_fmt ") failed!\n", \ __FILE__, __LINE__, #a, #cmp, #b, a_, #cmp, b_); \ cond; \ }) @@ -185,51 +187,75 @@ weston_assert_fail_(const struct weston_compositor *compositor, const char *fmt, #define weston_assert_bit_set(compositor, value, bit) \ ({ \ - struct weston_compositor *ec = compositor; \ + struct weston_compositor *wc_ = compositor; \ uint64_t v = (value); \ uint64_t b = (bit); \ bool cond = (v & b) == b; \ weston_assert_true(compositor, is_pow2_64(bit)); \ if (!cond) \ - custom_assert_fail_(ec, "%s:%u: Assertion failed! Bit \"%s\" (%" PRIu64 ") of \"%s\" (0x%" PRIx64 ") is not set.\n", \ + custom_assert_fail_(wc_, "%s:%u: Assertion failed! Bit \"%s\" (%" PRIu64 ") of \"%s\" (0x%" PRIx64 ") is not set.\n", \ __FILE__, __LINE__, #bit, b, #value, v); \ cond; \ }) #define weston_assert_bit_not_set(compositor, value, bit) \ ({ \ - struct weston_compositor *ec = compositor; \ + struct weston_compositor *wc_ = compositor; \ uint64_t v = (value); \ uint64_t b = (bit); \ bool cond = (v & b) == 0; \ weston_assert_true(compositor, is_pow2_64(bit)); \ if (!cond) \ - custom_assert_fail_(ec, "%s:%u: Assertion failed! Bit \"%s\" (%" PRIu64 ") of \"%s\" (0x%" PRIx64 ") is set.\n", \ + custom_assert_fail_(wc_, "%s:%u: Assertion failed! Bit \"%s\" (%" PRIu64 ") of \"%s\" (0x%" PRIx64 ") is set.\n", \ __FILE__, __LINE__, #bit, b, #value, v); \ cond; \ }) #define weston_assert_legal_bits(compositor, value, mask) \ ({ \ - struct weston_compositor *ec = compositor; \ + struct weston_compositor *wc_ = compositor; \ uint64_t v_ = (value); \ uint64_t m_ = (mask); \ uint64_t ill = v_ & ~m_; \ bool cond = ill == 0; \ if (!cond) \ - custom_assert_fail_(ec, "%s:%u: Assertion failed! " \ + custom_assert_fail_(wc_, "%s:%u: Assertion failed! " \ "Value %s (0x%" PRIx64 ") contains illegal bits 0x%" PRIx64 ". " \ "Legal mask is %s (0x%" PRIx64 ").\n", \ __FILE__, __LINE__, #value, v_, ill, #mask, m_); \ cond; \ }) +/* wl_list asserts */ + +#define weston_assert_list_empty(compositor, list) \ +({ \ + struct weston_compositor *wc_ = compositor; \ + struct wl_list *l_ = list; \ + bool cond = wl_list_empty(l_); \ + if (!cond) \ + custom_assert_fail_(wc_, "%s:%u: Assertion failed! wl_list '%s' is not empty.\n", \ + __FILE__, __LINE__, #list); \ + cond; \ +}) + +#define weston_assert_list_not_empty(compositor, list) \ +({ \ + struct weston_compositor *wc_ = compositor; \ + struct wl_list *l_ = list; \ + bool cond = !wl_list_empty(l_); \ + if (!cond) \ + custom_assert_fail_(wc_, "%s:%u: Assertion failed! wl_list '%s' is empty.\n", \ + __FILE__, __LINE__, #list); \ + cond; \ +}) + /* Misc asserts. */ #define weston_assert_not_reached(compositor, reason) \ do { \ - struct weston_compositor *ec = compositor; \ - custom_assert_fail_(ec, "%s:%u: Assertion failed! This should not be reached: %s\n", \ + struct weston_compositor *wc_ = compositor; \ + custom_assert_fail_(wc_, "%s:%u: Assertion failed! This should not be reached: %s\n", \ __FILE__, __LINE__, reason); \ } while (0) diff --git a/tests/assert-test.c b/tests/assert-test.c index c357c6529..2a8719c88 100644 --- a/tests/assert-test.c +++ b/tests/assert-test.c @@ -110,6 +110,35 @@ TEST(asserts_boolean) return RESULT_OK; } +TEST(asserts_list) +{ + /* Unused by the macros for now, so let's just use NULL. */ + struct weston_compositor *compositor = NULL; + struct wl_list list; + struct wl_list link; + bool ret; + + wl_list_init(&list); + + ret = weston_assert_list_empty(compositor, &list); + abort_if_not(ret); + ret = weston_assert_list_not_empty(compositor, &list); + abort_if_not(ret == false); + + wl_list_insert(&list, &link); + + ret = weston_assert_list_empty(compositor, &list); + abort_if_not(ret == false); + ret = weston_assert_list_not_empty(compositor, &list); + abort_if_not(ret); + + /* If we reach that point, it's a success so reset the assert counter + * that's been incremented to check that assertions work. */ + weston_assert_counter_reset(); + + return RESULT_OK; +} + TEST(asserts_pointer) { /* Unused by the macros for now, so let's just use NULL. */