wlroots/backend/drm/color.c

178 lines
5.2 KiB
C
Raw Normal View History

#include <stdlib.h>
#include "backend/drm/color.h"
#include "backend/drm/drm.h"
#include "render/color.h"
2025-11-13 14:31:14 +01:00
#include "util/matrix.h"
enum wlr_drm_crtc_color_transform_stage {
WLR_DRM_CRTC_COLOR_TRANSFORM_MATRIX,
WLR_DRM_CRTC_COLOR_TRANSFORM_LUT_3X1D,
};
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);
}
2025-11-13 14:31:14 +01:00
static bool set_stage(enum wlr_drm_crtc_color_transform_stage *current,
enum wlr_drm_crtc_color_transform_stage next) {
// Enforce that we fill the pipeline in order: first CTM then GAMMA_LUT
if (*current > next) {
return false;
}
*current = next;
return true;
}
static bool drm_crtc_color_transform_init_lut_3x1d(struct wlr_drm_crtc_color_transform *out,
2025-11-13 14:31:14 +01:00
enum wlr_drm_crtc_color_transform_stage *stage, size_t dim) {
if (!set_stage(stage, WLR_DRM_CRTC_COLOR_TRANSFORM_LUT_3X1D)) {
return false;
}
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,
2025-11-13 14:31:14 +01:00
struct wlr_color_transform *in, enum wlr_drm_crtc_color_transform_stage *stage,
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);
2025-11-13 14:31:14 +01:00
if (!drm_crtc_color_transform_init_lut_3x1d(out, stage, 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;
}
2025-11-13 14:31:14 +01:00
if (!drm_crtc_color_transform_init_lut_3x1d(out, stage, 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;
2025-11-13 14:31:14 +01:00
case COLOR_TRANSFORM_MATRIX:;
struct wlr_color_transform_matrix *matrix = wl_container_of(in, matrix, base);
if (!set_stage(stage, WLR_DRM_CRTC_COLOR_TRANSFORM_MATRIX)) {
return false;
}
wlr_matrix_multiply(out->matrix, matrix->matrix, out->matrix);
out->has_matrix = true;
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++) {
2025-11-13 14:31:14 +01:00
if (!drm_crtc_color_transform_convert(out, pipeline->transforms[i], stage, 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);
2025-11-13 14:31:14 +01:00
wlr_matrix_identity(tr->matrix);
size_t lut_3x1d_dim = drm_crtc_get_gamma_lut_size(backend, crtc);
2025-11-13 14:31:14 +01:00
enum wlr_drm_crtc_color_transform_stage stage = WLR_DRM_CRTC_COLOR_TRANSFORM_MATRIX;
tr->failed = !drm_crtc_color_transform_convert(tr, base, &stage, 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);
}