backend/drm: setup plane color pipeline

This commit is contained in:
Simon Ser 2026-06-04 17:00:33 +02:00
parent def08f373a
commit 9ba7d8c276
4 changed files with 294 additions and 0 deletions

View file

@ -292,6 +292,237 @@ static uint64_t pick_max_bpc(struct wlr_drm_connector *conn, struct wlr_drm_fb *
return target_bpc;
}
/** Convert a double to S31.32 sign-magnitude format */
static uint64_t to_fixed_s31_32(double v) {
uint64_t u = fabs(v) * ((uint64_t)1 << 32);
if (v < 0) {
u |= (uint64_t)1 << 63;
}
return u;
}
enum colorop_setup_status {
COLOROP_SETUP_SUCCESS,
COLOROP_SETUP_FAILURE,
COLOROP_SETUP_INCOMPATIBLE,
};
static enum colorop_setup_status setup_inverse_eotf_colorop(struct wlr_drm_backend *drm,
struct wlr_drm_colorop *colorop,
struct wlr_color_transform_inverse_eotf *inv_eotf,
struct wlr_drm_colorop_state *state) {
if (colorop->type != DRM_COLOROP_1D_CURVE) {
return COLOROP_SETUP_INCOMPATIBLE;
}
enum wlr_drm_colorop_curve_1d_type type = (uint32_t)-1;
switch (inv_eotf->tf) {
case WLR_COLOR_TRANSFER_FUNCTION_SRGB:
type = WLR_DRM_COLOROP_1D_CURVE_SRGB_INV_EOTF;
break;
case WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ:
break; // TODO: account for 125x scale
case WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR:
if (!colorop->props.bypass) {
return COLOROP_SETUP_INCOMPATIBLE;
}
state->bypass = true;
return COLOROP_SETUP_SUCCESS;
case WLR_COLOR_TRANSFER_FUNCTION_GAMMA22:
type = WLR_DRM_COLOROP_1D_CURVE_GAMMA22_INV;
break;
case WLR_COLOR_TRANSFER_FUNCTION_BT1886:
break; // not supported by KMS yet
}
assert(type != (uint32_t)-1);
if (!(colorop->curve_1d_types & (1 << type))) {
return COLOROP_SETUP_INCOMPATIBLE;
}
state->curve_1d_type = type;
return COLOROP_SETUP_SUCCESS;
}
static enum colorop_setup_status setup_matrix_colorop(struct wlr_drm_backend *drm,
struct wlr_drm_colorop *colorop,
const struct wlr_color_transform_matrix *tr_matrix,
struct wlr_drm_colorop_state *state) {
if (colorop->type != DRM_COLOROP_CTM_3X4) {
return COLOROP_SETUP_INCOMPATIBLE;
}
const float *matrix = tr_matrix->matrix;
struct drm_color_ctm_3x4 data = {
.matrix = {
to_fixed_s31_32(matrix[0]), to_fixed_s31_32(matrix[1]), to_fixed_s31_32(matrix[2]), 0,
to_fixed_s31_32(matrix[3]), to_fixed_s31_32(matrix[4]), to_fixed_s31_32(matrix[5]), 0,
to_fixed_s31_32(matrix[6]), to_fixed_s31_32(matrix[7]), to_fixed_s31_32(matrix[8]), 0,
},
};
uint32_t blob_id = 0;
if (drmModeCreatePropertyBlob(drm->fd, &data, sizeof(data), &blob_id) != 0) {
wlr_log_errno(WLR_ERROR, "Failed to create DATA property");
return COLOROP_SETUP_FAILURE;
}
state->data = blob_id;
return COLOROP_SETUP_SUCCESS;
}
static enum colorop_setup_status setup_lut_3x1d_colorop(struct wlr_drm_backend *drm,
struct wlr_drm_colorop *colorop,
const struct wlr_color_transform_lut_3x1d *lut_3x1d,
struct wlr_drm_colorop_state *state) {
if (colorop->type != DRM_COLOROP_1D_LUT || colorop->size != lut_3x1d->dim) {
return COLOROP_SETUP_INCOMPATIBLE;
}
size_t size = lut_3x1d->dim * sizeof(struct drm_color_lut32);
struct drm_color_lut32 *data = malloc(size);
if (data == NULL) {
return COLOROP_SETUP_FAILURE;
}
for (size_t i = 0; i < lut_3x1d->dim; i++) {
uint32_t factor = UINT32_MAX / UINT16_MAX;
data[i] = (struct drm_color_lut32){
.red = lut_3x1d->lut_3x1d[0 * lut_3x1d->dim + i] * factor,
.green = lut_3x1d->lut_3x1d[1 * lut_3x1d->dim + i] * factor,
.blue = lut_3x1d->lut_3x1d[2 * lut_3x1d->dim + i] * factor,
};
}
uint32_t blob_id = 0;
int ret = drmModeCreatePropertyBlob(drm->fd, data, size, &blob_id);
free(data);
if (ret != 0) {
wlr_log_errno(WLR_ERROR, "Failed to create DATA property");
return COLOROP_SETUP_FAILURE;
}
state->data = blob_id;
return COLOROP_SETUP_SUCCESS;
}
static enum colorop_setup_status setup_colorop(struct wlr_drm_backend *drm,
struct wlr_drm_colorop *colorop, struct wlr_color_transform *tr,
struct wlr_drm_colorop_state *state) {
switch (tr->type) {
case COLOR_TRANSFORM_INVERSE_EOTF:;
struct wlr_color_transform_inverse_eotf *inv_eotf =
wl_container_of(tr, inv_eotf, base);
return setup_inverse_eotf_colorop(drm, colorop, inv_eotf, state);
case COLOR_TRANSFORM_LCMS2:
return COLOROP_SETUP_INCOMPATIBLE; // TODO: setup 3D LUT
case COLOR_TRANSFORM_LUT_3X1D:;
struct wlr_color_transform_lut_3x1d *lut_3x1d =
wl_container_of(tr, lut_3x1d, base);
return setup_lut_3x1d_colorop(drm, colorop, lut_3x1d, state);
case COLOR_TRANSFORM_MATRIX:;
struct wlr_color_transform_matrix *matrix =
wl_container_of(tr, matrix, base);
return setup_matrix_colorop(drm, colorop, matrix, state);
case COLOR_TRANSFORM_PIPELINE:
break; // nested pipelines are not supported
}
return COLOROP_SETUP_INCOMPATIBLE;
}
static enum colorop_setup_status setup_color_pipeline(struct wlr_drm_backend *drm,
struct wl_list *pipeline, struct wlr_color_transform **transforms,
size_t transforms_len, struct wl_array *out_state_arr) {
struct wl_array state_arr = {0};
size_t i = 0;
struct wlr_drm_colorop *colorop;
wl_list_for_each(colorop, pipeline, link) {
struct wlr_drm_colorop_state *state = wl_array_add(&state_arr, sizeof(*state));
if (state == NULL) {
wl_array_release(&state_arr);
return COLOROP_SETUP_FAILURE;
}
state->colorop = colorop;
enum colorop_setup_status status = COLOROP_SETUP_INCOMPATIBLE;
if (i < transforms_len) {
status = setup_colorop(drm, colorop, transforms[i], state);
}
switch (status) {
case COLOROP_SETUP_SUCCESS:
i++;
break;
case COLOROP_SETUP_FAILURE:
return status;
case COLOROP_SETUP_INCOMPATIBLE:
if (!colorop->props.bypass) {
wl_array_release(&state_arr);
return COLOROP_SETUP_INCOMPATIBLE;
}
state->bypass = true;
break;
}
}
if (i < transforms_len) {
wl_array_release(&state_arr);
return COLOROP_SETUP_INCOMPATIBLE;
}
*out_state_arr = state_arr;
return COLOROP_SETUP_SUCCESS;
}
static bool pick_plane_color_pipeline(struct wlr_drm_backend *drm,
struct wlr_drm_plane *plane, struct wlr_drm_connector_state *state,
uint32_t *out_pipeline_id) {
struct wlr_color_transform *base_tr = state->base->pre_color_transform;
struct wlr_color_transform *tr = NULL;
if (!color_transform_compose(&tr, &base_tr, 1)) {
return false;
}
if (tr == NULL) {
*out_pipeline_id = 0;
return true;
}
struct wlr_color_transform **transforms;
size_t transforms_len;
if (tr->type == COLOR_TRANSFORM_PIPELINE) {
struct wlr_color_transform_pipeline *tr_pipeline =
wl_container_of(tr, tr_pipeline, base);
transforms = tr_pipeline->transforms;
transforms_len = tr_pipeline->len;
} else {
transforms = &tr;
transforms_len = 1;
}
for (size_t i = 0; i < plane->color_pipelines_len; i++) {
struct wl_list *pipeline = &plane->color_pipelines[i];
struct wl_array colorops = {0};
enum colorop_setup_status status =
setup_color_pipeline(drm, pipeline, transforms, transforms_len, &colorops);
switch (status) {
case COLOROP_SETUP_SUCCESS:;
struct wlr_drm_colorop *colorop =
wl_container_of(&pipeline->next, colorop, link);
*out_pipeline_id = colorop->id;
assert(state->colorops.size == 0);
state->colorops = colorops;
wlr_color_transform_unref(tr);
return true;
case COLOROP_SETUP_FAILURE:
wlr_color_transform_unref(tr);
return false;
case COLOROP_SETUP_INCOMPATIBLE:
break; // try the next pipeline
}
}
wlr_color_transform_unref(tr);
return false;
}
static void destroy_blob(struct wlr_drm_backend *drm, uint32_t id) {
if (id == 0) {
return;
@ -356,6 +587,13 @@ bool drm_atomic_connector_prepare(struct wlr_drm_connector_state *state, bool mo
}
}
uint32_t primary_color_pipeline = crtc->primary->color_pipeline;
if (state->base->committed & WLR_OUTPUT_STATE_PRE_COLOR_TRANSFORM) {
if (!pick_plane_color_pipeline(drm, crtc->primary, state, &primary_color_pipeline)) {
return false;
}
}
uint32_t fb_damage_clips = 0;
if ((state->base->committed & WLR_OUTPUT_STATE_DAMAGE) &&
crtc->primary->props.fb_damage_clips != 0) {
@ -401,6 +639,7 @@ bool drm_atomic_connector_prepare(struct wlr_drm_connector_state *state, bool mo
state->vrr_enabled = vrr_enabled;
state->colorspace = colorspace;
state->hdr_output_metadata = hdr_output_metadata;
state->primary_color_pipeline = primary_color_pipeline;
return true;
}
@ -426,6 +665,13 @@ void drm_atomic_connector_apply_commit(struct wlr_drm_connector_state *state) {
}
conn->colorspace = state->colorspace;
crtc->primary->color_pipeline = state->primary_color_pipeline;
struct wlr_drm_colorop_state *colorop_state;
wl_array_for_each(colorop_state, &state->colorops) {
commit_blob(drm, &colorop_state->colorop->data, colorop_state->data);
}
wl_array_release(&state->colorops);
}
void drm_atomic_connector_rollback_commit(struct wlr_drm_connector_state *state) {
@ -441,6 +687,12 @@ void drm_atomic_connector_rollback_commit(struct wlr_drm_connector_state *state)
if (state->primary_in_fence_fd >= 0) {
close(state->primary_in_fence_fd);
}
struct wlr_drm_colorop_state *colorop_state;
wl_array_for_each(colorop_state, &state->colorops) {
destroy_blob(drm, colorop_state->data);
}
wl_array_release(&state->colorops);
}
static void plane_disable(struct atomic *atom, struct wlr_drm_plane *plane) {
@ -596,6 +848,10 @@ static void atomic_connector_add(struct atomic *atom,
if (state->primary_in_fence_fd >= 0) {
set_plane_in_fence_fd(atom, crtc->primary, state->primary_in_fence_fd);
}
if (crtc->primary->props.color_pipeline != 0) {
atomic_add(atom, crtc->primary->id,
crtc->primary->props.color_pipeline, state->primary_color_pipeline);
}
if (crtc->cursor) {
if (drm_connector_is_cursor_visible(conn)) {
struct wlr_fbox cursor_src = {
@ -620,6 +876,23 @@ static void atomic_connector_add(struct atomic *atom,
plane_disable(atom, crtc->cursor);
}
}
struct wlr_drm_colorop_state *colorop_state;
wl_array_for_each(colorop_state, &state->colorops) {
struct wlr_drm_colorop *colorop = colorop_state->colorop;
if (colorop->props.bypass != 0) {
atomic_add(atom, colorop->id, colorop->props.bypass,
colorop_state->bypass);
}
if (colorop->props.curve_1d_type != 0) {
atomic_add(atom, colorop->id, colorop->props.curve_1d_type,
colorop_state->curve_1d_type);
}
if (colorop->props.data != 0) {
atomic_add(atom, colorop->id, colorop->props.data,
colorop_state->data);
}
}
} else {
plane_disable(atom, crtc->primary);
if (crtc->cursor) {

View file

@ -42,6 +42,7 @@ static const uint32_t COMMIT_OUTPUT_STATE =
WLR_OUTPUT_STATE_LAYERS |
WLR_OUTPUT_STATE_WAIT_TIMELINE |
WLR_OUTPUT_STATE_SIGNAL_TIMELINE |
WLR_OUTPUT_STATE_PRE_COLOR_TRANSFORM |
WLR_OUTPUT_STATE_POST_COLOR_TRANSFORM |
WLR_OUTPUT_STATE_IMAGE_DESCRIPTION |
WLR_OUTPUT_STATE_COLOR_REPRESENTATION;

View file

@ -79,6 +79,12 @@ static bool legacy_crtc_test(const struct wlr_drm_connector_state *state,
}
}
if (state->base->committed & WLR_OUTPUT_STATE_PRE_COLOR_TRANSFORM) {
wlr_drm_conn_log(conn, WLR_DEBUG,
"Pre-blending color pipeline not supported for legacy KMS API");
return false;
}
return true;
}

View file

@ -28,6 +28,8 @@ struct wlr_drm_colorop {
struct wlr_drm_colorop_props props;
struct wl_list link; // wlr_drm_plane.color_pipelines
uint32_t data; // for 1D_LUT, CTM_3X4, 3D_LUT
};
struct wlr_drm_plane {
@ -61,6 +63,9 @@ struct wlr_drm_plane {
uint32_t initial_crtc_id;
struct liftoff_plane *liftoff;
struct liftoff_layer *liftoff_layer;
// Atomic modesetting only
uint32_t color_pipeline;
};
struct wlr_drm_layer {
@ -177,6 +182,15 @@ struct wlr_drm_connector_state {
bool vrr_enabled;
uint32_t colorspace;
uint32_t hdr_output_metadata;
uint32_t primary_color_pipeline;
struct wl_array colorops; // struct wlr_drm_colorop_state
};
struct wlr_drm_colorop_state {
struct wlr_drm_colorop *colorop;
bool bypass;
uint32_t curve_1d_type; // for 1D_CURVE
uint32_t data; // for 1D_LUT, CTM_3X4, 3D_LUT
};
/**