diff --git a/frontend/main.c b/frontend/main.c index 7718d45b3..98a1f380c 100644 --- a/frontend/main.c +++ b/frontend/main.c @@ -3444,6 +3444,7 @@ load_drm_backend(struct weston_compositor *c, int *argc, char **argv, struct wet_backend *wb; bool without_input = false; bool force_pixman = false; + bool offload_blend_to_output = false; wet->drm_use_current_mode = false; @@ -3472,6 +3473,14 @@ load_drm_backend(struct weston_compositor *c, int *argc, char **argv, config.renderer = renderer; } + weston_config_section_get_bool(section, "offload-blend-to-output", + &offload_blend_to_output, false); + + if (!c->color_manager && offload_blend_to_output) + offload_blend_to_output = false; + + config.offload_blend_to_output = offload_blend_to_output; + weston_config_section_get_string(section, "gbm-format", &config.gbm_format, NULL); diff --git a/include/libweston/backend-drm.h b/include/libweston/backend-drm.h index d47955c7a..62cd3e69e 100644 --- a/include/libweston/backend-drm.h +++ b/include/libweston/backend-drm.h @@ -258,6 +258,14 @@ struct weston_drm_backend_config { * rendering device. */ char *additional_devices; + + /** Try to offload blend-to-output color transformation + * + * As KMS currently only supports to offload the transformation in a + * LUT, this may result in precision issues. Also, this requires + * "color-management" to be enabled. + */ + bool offload_blend_to_output; }; #ifdef __cplusplus diff --git a/libweston/backend-drm/drm-gbm.c b/libweston/backend-drm/drm-gbm.c index 06a985222..86cfb3034 100644 --- a/libweston/backend-drm/drm-gbm.c +++ b/libweston/backend-drm/drm-gbm.c @@ -320,9 +320,15 @@ enum format_alpha_required { FORMAT_ALPHA_NOT_REQUIRED = false, }; +enum format_component_type { + FORMAT_COMPONENT_TYPE_ANY, + FORMAT_COMPONENT_TYPE_FLOAT_ONLY, +}; + static const struct pixel_format_info * find_compatible_format(struct weston_compositor *compositor, struct wl_array *formats, int min_bpc, + enum format_component_type component_type, enum format_alpha_required alpha_required) { const struct pixel_format_info **tmp, *p; @@ -331,9 +337,10 @@ find_compatible_format(struct weston_compositor *compositor, /** * Given a format array, this looks for a format respecting a few * criteria. First of all, this ignores formats that do not contain an - * alpha channel when alpha_required == FORMAT_ALPHA_REQUIRED. Also, it - * ignores formats that do not have bits per color channel (bpc) bigger - * or equal to min_bpc. + * alpha channel when alpha_required == FORMAT_ALPHA_REQUIRED. Similar + * for formats that are not floating point when component_type == + * FORMAT_COMPONENT_TYPE_FLOAT_ONLY. Also, it ignores formats that do + * not have bits per color channel (bpc) bigger or equal to min_bpc. * * When we have multiple formats matching these criteria, we use the * following to choose: @@ -349,6 +356,9 @@ find_compatible_format(struct weston_compositor *compositor, p = *tmp; /* Skip candidates that do not match minimum criteria. */ + if (component_type == FORMAT_COMPONENT_TYPE_FLOAT_ONLY && + p->component_type != PIXEL_COMPONENT_TYPE_FLOAT) + continue; if (alpha_required == FORMAT_ALPHA_REQUIRED && p->bits.a == 0) continue; if (p->bits.r < min_bpc || p->bits.g < min_bpc || p->bits.b < min_bpc) @@ -393,6 +403,7 @@ drm_output_pick_format_egl(struct drm_output *output) const struct pixel_format_info **f; unsigned int renderer_formats_count; struct wl_array supported_formats; + enum format_component_type component_type; uint32_t min_bpc; unsigned int i; bool ret = true; @@ -417,7 +428,11 @@ drm_output_pick_format_egl(struct drm_output *output) *f = renderer_formats[i]; } - if (output->base.eotf_mode != WESTON_EOTF_MODE_SDR) { + if (output->base.from_blend_to_output_by_backend) { + component_type = FORMAT_COMPONENT_TYPE_FLOAT_ONLY; + min_bpc = 16; + } else if (output->base.eotf_mode != WESTON_EOTF_MODE_SDR) { + component_type = FORMAT_COMPONENT_TYPE_ANY; min_bpc = 10; } else { /** @@ -431,7 +446,8 @@ drm_output_pick_format_egl(struct drm_output *output) if (b->has_underlay) { output->format = find_compatible_format(compositor, &supported_formats, - min_bpc, FORMAT_ALPHA_REQUIRED); + min_bpc, component_type, + FORMAT_ALPHA_REQUIRED); if (output->format) goto done; @@ -443,7 +459,8 @@ drm_output_pick_format_egl(struct drm_output *output) output->format = find_compatible_format(compositor, &supported_formats, - min_bpc, FORMAT_ALPHA_NOT_REQUIRED); + min_bpc, component_type, + FORMAT_ALPHA_NOT_REQUIRED); if (output->format) goto done; diff --git a/libweston/backend-drm/drm-internal.h b/libweston/backend-drm/drm-internal.h index 23fe9ab32..cfa19ffed 100644 --- a/libweston/backend-drm/drm-internal.h +++ b/libweston/backend-drm/drm-internal.h @@ -243,6 +243,9 @@ 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_backend { @@ -264,6 +267,8 @@ struct drm_backend { bool use_pixman_shadow; + bool offload_blend_to_output; + struct udev_input input; uint32_t pageflip_timeout; @@ -519,6 +524,19 @@ 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; @@ -549,6 +567,9 @@ struct drm_crtc { /* Holds the properties for the CRTC */ struct drm_property_info props_crtc[WDRM_CRTC__COUNT]; + + /* CRTC prop WDRM_CRTC_GAMMA_LUT_SIZE */ + uint32_t lut_size; }; struct drm_output { @@ -588,6 +609,7 @@ struct drm_output { bool legacy_gamma_not_supported; uint16_t legacy_gamma_size; + struct drm_colorop_3x1d_lut *blend_to_output_xform; /* Plane being displayed directly on the CRTC */ struct drm_plane *scanout_plane; diff --git a/libweston/backend-drm/drm.c b/libweston/backend-drm/drm.c index de9fa3c9c..4380f4c78 100644 --- a/libweston/backend-drm/drm.c +++ b/libweston/backend-drm/drm.c @@ -56,7 +56,9 @@ #include "shared/helpers.h" #include "shared/timespec-util.h" #include "shared/string-helpers.h" +#include "shared/weston-assert.h" #include "shared/weston-drm-fourcc.h" +#include "shared/xalloc.h" #include "output-capture.h" #include "weston-trace.h" #include "pixman-renderer.h" @@ -1764,8 +1766,10 @@ drm_output_pick_format_pixman(struct drm_output *output) struct drm_device *device = output->device; struct drm_backend *b = device->backend; - /* Any other value of eotf_mode requires color-management, which is not - * supported by Pixman renderer. */ + /* Any other value of eotf_mode requires color management, which is also + * necessary to have from_blend_to_output_by_backend set. Color + * management is unsupported by Pixman renderer. */ + assert(!output->base.from_blend_to_output_by_backend); assert(output->base.eotf_mode == WESTON_EOTF_MODE_SDR); if (!b->format->pixman_format) { @@ -2079,6 +2083,169 @@ 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->drm.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 float * +lut_3x1d_from_blend_to_output(struct weston_compositor *compositor, + struct weston_color_transform *xform, + uint32_t len_lut, char **err_msg) +{ + /** + * 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. + */ + if (!xform->steps_valid) { + str_printf(err_msg, "xform color steps are invalid"); + return NULL; + } + + /** + * We expect blend-to-output to be composed of pre-curve only. We could + * handle a post-curve as well (merging the pre-curve and post-curve), + * but that's not necessary. + */ + if (xform->post_curve.type != WESTON_COLOR_CURVE_TYPE_IDENTITY || + xform->mapping.type != WESTON_COLOR_MAPPING_TYPE_IDENTITY) { + str_printf(err_msg, "xform unexpectedly has more steps than pre-curve"); + return NULL; + } + + /** + * No need to craft LUT 3x1D from identity. But there shouldn't be a + * blend-to-output xform like this in first place. + */ + weston_assert_uint32_neq(compositor, xform->pre_curve.type, + WESTON_COLOR_CURVE_TYPE_IDENTITY); + + return weston_color_curve_to_3x1D_LUT(compositor, xform, + WESTON_COLOR_CURVE_STEP_PRE, + WESTON_COLOR_PRECISION_CARELESS, + len_lut, err_msg); +} + +static int +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 weston_color_transform *xform; + struct drm_color_lut *drm_lut; + uint64_t lut_size; + uint32_t gamma_lut_blob_id; + float *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); + xform = output->base.color_outcome->from_blend_to_output; + if (!xform) + return 0; + + lut_size = output->crtc->lut_size; + if (lut_size == 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 + * case, we make use of it. + */ + colorop_lut = drm_colorop_3x1d_lut_search(device, xform, lut_size); + 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); + if (!cm_lut) { + drm_debug(b, "[output] failed to create 3x1D LUT for blend-to-output: %s\n", + err_msg); + free(err_msg); + return -1; + } + + drm_lut = xzalloc(lut_size * sizeof(*drm_lut)); + for (i = 0; i < lut_size; i++) { + drm_lut[i].red = cm_lut[i] * 0xffff; + drm_lut[i].green = cm_lut[i + lut_size] * 0xffff; + drm_lut[i].blue = cm_lut[i + 2 * lut_size] * 0xffff; + } + free(cm_lut); + ret = drmModeCreatePropertyBlob(device->drm.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"); + return -1; + } + + output->blend_to_output_xform = + drm_colorop_3x1d_lut_create(xform, device, lut_size, + gamma_lut_blob_id); + return 0; +} + enum writeback_screenshot_state drm_output_get_writeback_state(struct drm_output *output) { @@ -2225,7 +2392,10 @@ drm_crtc_create(struct drm_device *device, uint32_t crtc_id, uint32_t pipe) crtc->device = device; crtc->crtc_id = crtc_id; crtc->pipe = pipe; - crtc->output = NULL; + + crtc->lut_size = + drm_property_get_value(&crtc->props_crtc[WDRM_CRTC_GAMMA_LUT_SIZE], + props, 0); /* Add it to the last position of the DRM-backend CRTC list */ wl_list_insert(device->crtc_list.prev, &crtc->link); @@ -2518,6 +2688,11 @@ drm_output_enable(struct weston_output *base) if (drm_output_init_legacy_gamma_size(output) < 0) goto err_planes; + output->base.from_blend_to_output_by_backend = b->offload_blend_to_output; + if (output->base.from_blend_to_output_by_backend && + drm_output_pick_blend_to_output(output) < 0) + goto err_planes; + if (b->pageflip_timeout) drm_output_pageflip_timer_create(output); @@ -2588,6 +2763,9 @@ drm_output_deinit(struct weston_output *base) drm_output_deinit_planes(output); drm_output_detach_crtc(output); + output->blend_to_output_xform = NULL; + output->base.from_blend_to_output_by_backend = false; + if (output->hdr_output_metadata_blob_id) { drmModeDestroyPropertyBlob(device->drm.fd, output->hdr_output_metadata_blob_id); @@ -3691,6 +3869,8 @@ drm_destroy(struct weston_backend *backend) &b->drm->writeback_connector_list, link) drm_writeback_destroy(writeback); + weston_assert_true(ec, wl_list_empty(&b->drm->drm_colorop_3x1d_lut_list)); + #ifdef BUILD_DRM_GBM if (b->gbm) gbm_device_destroy(b->gbm); @@ -4128,6 +4308,8 @@ drm_device_create(struct drm_backend *backend, const char *name) wl_list_init(&device->plane_list); create_sprites(device); + wl_list_init(&device->drm_colorop_3x1d_lut_list); + wl_list_init(&device->writeback_connector_list); if (drm_backend_discover_connectors(device, udev_device, res) < 0) { weston_log("Failed to create heads for %s\n", device->drm.filename); @@ -4218,6 +4400,7 @@ drm_backend_create(struct weston_compositor *compositor, b->compositor = compositor; b->pageflip_timeout = config->pageflip_timeout; b->use_pixman_shadow = config->use_pixman_shadow; + b->offload_blend_to_output = config->offload_blend_to_output; b->has_underlay = false; b->debug = weston_compositor_add_log_scope(compositor, "drm-backend", @@ -4331,6 +4514,8 @@ drm_backend_create(struct weston_compositor *compositor, wl_list_init(&device->plane_list); create_sprites(b->drm); + wl_list_init(&device->drm_colorop_3x1d_lut_list); + if (udev_input_init(&b->input, compositor, b->udev, seat_id, config->configure_device) < 0) { diff --git a/libweston/backend-drm/kms.c b/libweston/backend-drm/kms.c index a80bea944..f5607fbb7 100644 --- a/libweston/backend-drm/kms.c +++ b/libweston/backend-drm/kms.c @@ -1321,7 +1321,13 @@ drm_output_apply_state_atomic(struct drm_output_state *state, current_mode->blob_id); ret |= crtc_add_prop(req, crtc, WDRM_CRTC_ACTIVE, 1); - ret |= crtc_add_prop_zero_ok(req, crtc, WDRM_CRTC_GAMMA_LUT, 0); + if (output->base.from_blend_to_output_by_backend && + output->blend_to_output_xform) + ret |= crtc_add_prop(req, crtc, WDRM_CRTC_GAMMA_LUT, + output->blend_to_output_xform->blob_id); + else + ret |= crtc_add_prop_zero_ok(req, crtc, WDRM_CRTC_GAMMA_LUT, 0); + ret |= crtc_add_prop_zero_ok(req, crtc, WDRM_CRTC_DEGAMMA_LUT, 0); ret |= crtc_add_prop_zero_ok(req, crtc, WDRM_CRTC_CTM, 0); diff --git a/libweston/compositor.c b/libweston/compositor.c index da4f2b509..7d6f7029c 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -7777,6 +7777,21 @@ weston_output_set_color_outcome(struct weston_output *output) assert(output->color_profile); + if (output->color_outcome && output->from_blend_to_output_by_backend) { + /** + * For now we can't allow changing the output color profile and + * color outcome when the backend is offloading the + * blend-to-output xform. We don't know if backend would be able + * to offload the new blend-to-output, so we'd need to fallback + * to the renderer. But that depends on some setup that we do + * when starting the DRM-backend and GL-renderer. + */ + weston_log("Error: can't change color outcome for output \"%s\":\n" \ + "output->from_blend_to_output_by_backend is true.\n", + output->name); + return false; + } + colorout = cm->create_output_color_outcome(cm, output); if (!colorout) { weston_log("Creating color transformation for output \"%s\" failed.\n", @@ -7794,8 +7809,6 @@ weston_output_set_color_outcome(struct weston_output *output) output->color_outcome = colorout; output->color_outcome_serial++; - output->from_blend_to_output_by_backend = false; - weston_log("Output '%s' using color profile: %s\n", output->name, weston_color_profile_get_description(output->color_profile)); diff --git a/man/weston-drm.man b/man/weston-drm.man index 3fd524fed..1a40c6c1f 100644 --- a/man/weston-drm.man +++ b/man/weston-drm.man @@ -71,6 +71,16 @@ would be needed for Weston to display content lifted into such hardware planes. sets Weston's pageflip timeout in milliseconds. This sets a timer to exit gracefully with a log message and an exit code of 1 in case the DRM driver is non-responsive. Setting it to 0 disables this feature. +.TP +\fBoffload-blend-to-output\fR=\fItrue\fR +Allow backend to offload the blend-to-output color transformation (whenever +possible) instead of performing this transformation in the renderer. Only the +DRM-backend is capable of doing that at the moment. Requires color management +enabled. Boolean, defaults to +.BR false . + +.I CAVEAT: +This may result in loss of color precision and may cause color banding. .SS Section output .TP