backend-drm: introduce color pipelines

This adds support to the kernel proposal that introduces per-plane color
pipelines. For now it only manages the lifetime of the kernel objects
related to that.

In the next commits we start using all that to offload Weston pre-blend
color transformations to KMS.

Signed-off-by: Leandro Ribeiro <leandro.ribeiro@collabora.com>
This commit is contained in:
Leandro Ribeiro 2025-04-29 13:17:26 -03:00
parent afc70eda78
commit c186e17cb2
8 changed files with 488 additions and 0 deletions

View file

@ -0,0 +1,243 @@
/*
* 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 "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_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;
}
static const char *
drm_colorop_type_to_str(struct drm_colorop *colorop)
{
return colorop->props[WDRM_COLOROP_TYPE].enum_values[colorop->type].name;
}
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;
}

View file

@ -0,0 +1,77 @@
/*
* 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 {
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];
};
struct drm_color_pipeline {
struct drm_plane *plane;
struct wl_list colorop_list; /* drm_colorop::link */
uint32_t id;
};
#if CAN_OFFLOAD_COLOR_PIPELINE
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_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 */

View file

@ -27,6 +27,8 @@
* SOFTWARE.
*/
#pragma once
#include "config.h"
#include <errno.h>
@ -227,6 +229,8 @@ struct drm_device {
bool aspect_ratio_supported;
bool color_pipeline_supported;
int32_t cursor_width;
int32_t cursor_height;
@ -491,6 +495,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 {
@ -869,6 +877,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);

View file

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

View file

@ -51,6 +51,7 @@
#include <libweston/libweston.h>
#include <libweston/backend-drm.h>
#include <libweston/weston-log.h>
#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);

View file

@ -91,6 +91,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 +137,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 +149,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",
@ -2079,6 +2145,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;

View file

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

View file

@ -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',
[