diff --git a/backend/drm/atomic.c b/backend/drm/atomic.c index 41773d4f5..a694b06d1 100644 --- a/backend/drm/atomic.c +++ b/backend/drm/atomic.c @@ -6,6 +6,7 @@ #include #include #include +#include "backend/drm/color.h" #include "backend/drm/drm.h" #include "backend/drm/fb.h" #include "backend/drm/iface.h" @@ -334,11 +335,9 @@ bool drm_atomic_connector_prepare(struct wlr_drm_connector_state *state, bool mo if (state->base->committed & WLR_OUTPUT_STATE_COLOR_TRANSFORM) { size_t dim = 0; uint16_t *lut = NULL; - if (state->base->color_transform != NULL) { - struct wlr_color_transform_lut_3x1d *tr = - color_transform_lut_3x1d_from_base(state->base->color_transform); - dim = tr->dim; - lut = tr->lut_3x1d; + if (state->crtc_color_transform != NULL) { + dim = state->crtc_color_transform->lut_3x1d->dim; + lut = state->crtc_color_transform->lut_3x1d->lut_3x1d; } // Fallback to legacy gamma interface when gamma properties are not diff --git a/backend/drm/color.c b/backend/drm/color.c new file mode 100644 index 000000000..8d86b28eb --- /dev/null +++ b/backend/drm/color.c @@ -0,0 +1,147 @@ +#include + +#include "backend/drm/color.h" +#include "backend/drm/drm.h" +#include "render/color.h" + +static struct wlr_color_transform_lut_3x1d *create_identity_3x1dlut(size_t dim) { + if (dim == 0) { + return NULL; + } + uint16_t *lut = malloc(dim * sizeof(lut[0])); + if (lut == NULL) { + return NULL; + } + for (size_t i = 0; i < dim; i++) { + float x = (float)i / (dim - 1); + lut[i] = (uint16_t)roundf(UINT16_MAX * x); + } + struct wlr_color_transform *out = wlr_color_transform_init_lut_3x1d(dim, lut, lut, lut); + free(lut); + return color_transform_lut_3x1d_from_base(out); +} + +static bool drm_crtc_color_transform_init_lut_3x1d(struct wlr_drm_crtc_color_transform *out, + size_t dim) { + if (out->lut_3x1d != NULL) { + return true; + } + out->lut_3x1d = create_identity_3x1dlut(dim); + return out->lut_3x1d != NULL; +} + +static bool drm_crtc_color_transform_convert(struct wlr_drm_crtc_color_transform *out, + struct wlr_color_transform *in, size_t lut_3x1d_dim) { + switch (in->type) { + case COLOR_TRANSFORM_INVERSE_EOTF:; + struct wlr_color_transform_inverse_eotf *inv_eotf = + wlr_color_transform_inverse_eotf_from_base(in); + + if (!drm_crtc_color_transform_init_lut_3x1d(out, lut_3x1d_dim)) { + return false; + } + + for (size_t i = 0; i < 3; i++) { + for (size_t j = 0; j < lut_3x1d_dim; j++) { + size_t offset = i * lut_3x1d_dim + j; + float v = (float)out->lut_3x1d->lut_3x1d[offset] / UINT16_MAX; + v = wlr_color_transfer_function_eval_inverse_eotf(inv_eotf->tf, v); + out->lut_3x1d->lut_3x1d[offset] = (uint16_t)roundf(UINT16_MAX * v); + } + } + + return true; + case COLOR_TRANSFORM_LUT_3X1D:; + struct wlr_color_transform_lut_3x1d *lut_3x1d = + color_transform_lut_3x1d_from_base(in); + if (lut_3x1d->dim != lut_3x1d_dim) { + return false; + } + + if (!drm_crtc_color_transform_init_lut_3x1d(out, lut_3x1d_dim)) { + return false; + } + + // TODO: we loose precision when the input color transform is a lone 3×1D LUT + for (size_t i = 0; i < 3 * lut_3x1d->dim; i++) { + out->lut_3x1d->lut_3x1d[i] *= lut_3x1d->lut_3x1d[i]; + } + + return true; + case COLOR_TRANSFORM_LCMS2: + return false; // unsupported + case COLOR_TRANSFORM_PIPELINE:; + struct wlr_color_transform_pipeline *pipeline = wl_container_of(in, pipeline, base); + + for (size_t i = 0; i < pipeline->len; i++) { + if (!drm_crtc_color_transform_convert(out, pipeline->transforms[i], lut_3x1d_dim)) { + return false; + } + } + + return true; + } + abort(); // unreachable +} + +static void addon_destroy(struct wlr_addon *addon) { + struct wlr_drm_crtc_color_transform *tr = wl_container_of(addon, tr, addon); + if (tr->lut_3x1d != NULL) { + wlr_color_transform_unref(&tr->lut_3x1d->base); + } + free(tr); +} + +static const struct wlr_addon_interface addon_impl = { + .name = "wlr_drm_crtc_color_transform", + .destroy = addon_destroy, +}; + +static struct wlr_drm_crtc_color_transform *drm_crtc_color_transform_create( + struct wlr_drm_backend *backend, struct wlr_drm_crtc *crtc, + struct wlr_color_transform *base) { + struct wlr_drm_crtc_color_transform *tr = calloc(1, sizeof(*tr)); + if (tr == NULL) { + return NULL; + } + + tr->base = base; + wlr_addon_init(&tr->addon, &base->addons, crtc, &addon_impl); + + size_t lut_3x1d_dim = drm_crtc_get_gamma_lut_size(backend, crtc); + tr->failed = !drm_crtc_color_transform_convert(tr, base, lut_3x1d_dim); + + return tr; +} + +struct wlr_drm_crtc_color_transform *drm_crtc_color_transform_import( + struct wlr_drm_backend *backend, struct wlr_drm_crtc *crtc, + struct wlr_color_transform *base) { + struct wlr_drm_crtc_color_transform *tr; + struct wlr_addon *addon = wlr_addon_find(&base->addons, crtc, &addon_impl); + if (addon != NULL) { + tr = wl_container_of(addon, tr, addon); + } else { + tr = drm_crtc_color_transform_create(backend, crtc, base); + if (tr == NULL) { + return NULL; + } + } + + if (tr->failed) { + // We failed to convert the color transform to a 3×1D LUT. Keep the + // addon attached so that we remember that this color transform cannot + // be imported next time a commit contains it. + return NULL; + } + + wlr_color_transform_ref(tr->base); + return tr; +} + +void drm_crtc_color_transform_unref(struct wlr_drm_crtc_color_transform *tr) { + if (tr == NULL) { + return; + } + wlr_color_transform_unref(tr->base); +} diff --git a/backend/drm/drm.c b/backend/drm/drm.c index 86b52c684..f347f7a43 100644 --- a/backend/drm/drm.c +++ b/backend/drm/drm.c @@ -20,6 +20,7 @@ #include #include #include +#include "backend/drm/color.h" #include "backend/drm/drm.h" #include "backend/drm/fb.h" #include "backend/drm/iface.h" @@ -707,6 +708,7 @@ static void drm_connector_state_finish(struct wlr_drm_connector_state *state) { drm_fb_clear(&state->primary_fb); drm_fb_clear(&state->cursor_fb); wlr_drm_syncobj_timeline_unref(state->wait_timeline); + drm_crtc_color_transform_unref(state->crtc_color_transform); } static bool drm_connector_state_update_primary_fb(struct wlr_drm_connector *conn, @@ -858,11 +860,15 @@ static bool drm_connector_prepare(struct wlr_drm_connector_state *conn_state, bo } } - if ((state->committed & WLR_OUTPUT_STATE_COLOR_TRANSFORM) && state->color_transform != NULL && - state->color_transform->type != COLOR_TRANSFORM_LUT_3X1D) { - wlr_drm_conn_log(conn, WLR_DEBUG, - "Only 3x1D LUT color transforms are supported"); - return false; + if ((state->committed & WLR_OUTPUT_STATE_COLOR_TRANSFORM) && state->color_transform != NULL) { + assert(conn_state->crtc_color_transform == NULL); + conn_state->crtc_color_transform = drm_crtc_color_transform_import(conn->backend, + conn->crtc, state->color_transform); + if (conn_state->crtc_color_transform == NULL) { + wlr_drm_conn_log(conn, WLR_DEBUG, + "Failed to import color transform"); + return false; + } } if ((state->committed & WLR_OUTPUT_STATE_IMAGE_DESCRIPTION) && diff --git a/backend/drm/legacy.c b/backend/drm/legacy.c index 223852ec1..972c913bd 100644 --- a/backend/drm/legacy.c +++ b/backend/drm/legacy.c @@ -3,6 +3,7 @@ #include #include #include +#include "backend/drm/color.h" #include "backend/drm/drm.h" #include "backend/drm/fb.h" #include "backend/drm/iface.h" @@ -128,11 +129,9 @@ static bool legacy_crtc_commit(const struct wlr_drm_connector_state *state, if (state->base->committed & WLR_OUTPUT_STATE_COLOR_TRANSFORM) { size_t dim = 0; uint16_t *lut = NULL; - if (state->base->color_transform != NULL) { - struct wlr_color_transform_lut_3x1d *tr = - color_transform_lut_3x1d_from_base(state->base->color_transform); - dim = tr->dim; - lut = tr->lut_3x1d; + if (state->crtc_color_transform != NULL) { + dim = state->crtc_color_transform->lut_3x1d->dim; + lut = state->crtc_color_transform->lut_3x1d->lut_3x1d; } if (!drm_legacy_crtc_set_gamma(drm, crtc, dim, lut)) { diff --git a/backend/drm/meson.build b/backend/drm/meson.build index d9e5b77f5..0f02588be 100644 --- a/backend/drm/meson.build +++ b/backend/drm/meson.build @@ -38,6 +38,7 @@ wlr_files += pnpids_c wlr_files += files( 'atomic.c', 'backend.c', + 'color.c', 'drm.c', 'fb.c', 'legacy.c', diff --git a/include/backend/drm/color.h b/include/backend/drm/color.h new file mode 100644 index 000000000..63f5377d1 --- /dev/null +++ b/include/backend/drm/color.h @@ -0,0 +1,23 @@ +#ifndef BACKEND_DRN_COLOR_H +#define BACKEND_DRN_COLOR_H + +#include + +struct wlr_drm_backend; +struct wlr_drm_crtc; +struct wlr_color_transform; + +struct wlr_drm_crtc_color_transform { + struct wlr_color_transform *base; + struct wlr_addon addon; // wlr_color_transform.addons + bool failed; + struct wlr_color_transform_lut_3x1d *lut_3x1d; +}; + +struct wlr_drm_crtc_color_transform *drm_crtc_color_transform_import( + struct wlr_drm_backend *backend, struct wlr_drm_crtc *crtc, + struct wlr_color_transform *base); + +void drm_crtc_color_transform_unref(struct wlr_drm_crtc_color_transform *tr); + +#endif diff --git a/include/backend/drm/drm.h b/include/backend/drm/drm.h index af4231f54..90839bbad 100644 --- a/include/backend/drm/drm.h +++ b/include/backend/drm/drm.h @@ -15,6 +15,8 @@ #include "backend/drm/properties.h" #include "backend/drm/renderer.h" +struct wlr_drm_crtc_color_transform; + struct wlr_drm_viewport { struct wlr_fbox src_box; struct wlr_box dst_box; @@ -152,6 +154,8 @@ struct wlr_drm_connector_state { struct wlr_drm_syncobj_timeline *wait_timeline; uint64_t wait_point; + struct wlr_drm_crtc_color_transform *crtc_color_transform; + // used by atomic uint32_t mode_id; uint32_t gamma_lut;