backend-drm: offload post-blend color transformation to KMS

In this patch we allow offloading the post-blend color transformation
to KMS.

As KMS currently only supports to offload the transformation in a LUT,
this may result in precision issues. So this introduces the config
option "offload-blend-to-output", which is disabled by default.

This option requires "color-management" to be enabled, otherwise it is
ignored.

Signed-off-by: Leandro Ribeiro <leandro.ribeiro@collabora.com>
This commit is contained in:
Leandro Ribeiro 2024-10-04 15:50:30 -03:00
parent 996ec58ce3
commit c594aa22cb
8 changed files with 282 additions and 12 deletions

View file

@ -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);

View file

@ -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

View file

@ -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;

View file

@ -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;

View file

@ -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) {

View file

@ -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);

View file

@ -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));

View file

@ -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