From 711ae922a8203240284c24e7d0572ff71ac5f16c Mon Sep 17 00:00:00 2001 From: Leandro Ribeiro Date: Mon, 16 Mar 2026 18:01:00 -0300 Subject: [PATCH 01/16] shared: rename ec to wc_ for assert macros We usually use the variable name 'ec' for struct weston_compositor. We currently use the same in our assert macros, bringing issues. Rename 'ec' to 'wc_' in our assert macros, avoiding this kind of issue. Also, this fixes a weston_assert_ macro call in which we forgot to declare 'ec' before calling it. Signed-off-by: Leandro Ribeiro --- libweston/backend-drm/drm.c | 1 + shared/weston-assert.h | 24 ++++++++++++------------ 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/libweston/backend-drm/drm.c b/libweston/backend-drm/drm.c index 1d2bd3901..6e52a0cda 100644 --- a/libweston/backend-drm/drm.c +++ b/libweston/backend-drm/drm.c @@ -4200,6 +4200,7 @@ drm_kms_device_destroy(struct drm_kms_device *kms_device) static void drm_device_destroy(struct drm_device *device) { + struct weston_compositor *ec = device->backend->compositor; struct drm_crtc *crtc, *crtc_tmp; struct drm_writeback *writeback, *writeback_tmp; diff --git a/shared/weston-assert.h b/shared/weston-assert.h index f72752acc..905f2c5de 100644 --- a/shared/weston-assert.h +++ b/shared/weston-assert.h @@ -53,24 +53,24 @@ weston_assert_fail_(const struct weston_compositor *compositor, const char *fmt, #define weston_assert_(compositor, a, b, val_type, val_fmt, cmp) \ ({ \ - struct weston_compositor *ec = compositor; \ + struct weston_compositor *wc_ = compositor; \ val_type a_ = (a); \ val_type b_ = (b); \ bool cond = a_ cmp b_; \ if (!cond) \ - custom_assert_fail_(ec, "%s:%u: Assertion %s %s %s (" val_fmt " %s " val_fmt ") failed!\n", \ + custom_assert_fail_(wc_, "%s:%u: Assertion %s %s %s (" val_fmt " %s " val_fmt ") failed!\n", \ __FILE__, __LINE__, #a, #cmp, #b, a_, #cmp, b_); \ cond; \ }) #define weston_assert_fn_(compositor, fn, a, b, val_type, val_fmt, cmp) \ ({ \ - struct weston_compositor *ec = compositor; \ + struct weston_compositor *wc_ = compositor; \ val_type a_ = (a); \ val_type b_ = (b); \ bool cond = fn(a_, b_) cmp 0; \ if (!cond) \ - custom_assert_fail_(ec, "%s:%u: Assertion %s %s %s (" val_fmt " %s " val_fmt ") failed!\n", \ + custom_assert_fail_(wc_, "%s:%u: Assertion %s %s %s (" val_fmt " %s " val_fmt ") failed!\n", \ __FILE__, __LINE__, #a, #cmp, #b, a_, #cmp, b_); \ cond; \ }) @@ -185,39 +185,39 @@ weston_assert_fail_(const struct weston_compositor *compositor, const char *fmt, #define weston_assert_bit_set(compositor, value, bit) \ ({ \ - struct weston_compositor *ec = compositor; \ + struct weston_compositor *wc_ = compositor; \ uint64_t v = (value); \ uint64_t b = (bit); \ bool cond = (v & b) == b; \ weston_assert_true(compositor, is_pow2_64(bit)); \ if (!cond) \ - custom_assert_fail_(ec, "%s:%u: Assertion failed! Bit \"%s\" (%" PRIu64 ") of \"%s\" (0x%" PRIx64 ") is not set.\n", \ + custom_assert_fail_(wc_, "%s:%u: Assertion failed! Bit \"%s\" (%" PRIu64 ") of \"%s\" (0x%" PRIx64 ") is not set.\n", \ __FILE__, __LINE__, #bit, b, #value, v); \ cond; \ }) #define weston_assert_bit_not_set(compositor, value, bit) \ ({ \ - struct weston_compositor *ec = compositor; \ + struct weston_compositor *wc_ = compositor; \ uint64_t v = (value); \ uint64_t b = (bit); \ bool cond = (v & b) == 0; \ weston_assert_true(compositor, is_pow2_64(bit)); \ if (!cond) \ - custom_assert_fail_(ec, "%s:%u: Assertion failed! Bit \"%s\" (%" PRIu64 ") of \"%s\" (0x%" PRIx64 ") is set.\n", \ + custom_assert_fail_(wc_, "%s:%u: Assertion failed! Bit \"%s\" (%" PRIu64 ") of \"%s\" (0x%" PRIx64 ") is set.\n", \ __FILE__, __LINE__, #bit, b, #value, v); \ cond; \ }) #define weston_assert_legal_bits(compositor, value, mask) \ ({ \ - struct weston_compositor *ec = compositor; \ + struct weston_compositor *wc_ = compositor; \ uint64_t v_ = (value); \ uint64_t m_ = (mask); \ uint64_t ill = v_ & ~m_; \ bool cond = ill == 0; \ if (!cond) \ - custom_assert_fail_(ec, "%s:%u: Assertion failed! " \ + custom_assert_fail_(wc_, "%s:%u: Assertion failed! " \ "Value %s (0x%" PRIx64 ") contains illegal bits 0x%" PRIx64 ". " \ "Legal mask is %s (0x%" PRIx64 ").\n", \ __FILE__, __LINE__, #value, v_, ill, #mask, m_); \ @@ -228,8 +228,8 @@ weston_assert_fail_(const struct weston_compositor *compositor, const char *fmt, #define weston_assert_not_reached(compositor, reason) \ do { \ - struct weston_compositor *ec = compositor; \ - custom_assert_fail_(ec, "%s:%u: Assertion failed! This should not be reached: %s\n", \ + struct weston_compositor *wc_ = compositor; \ + custom_assert_fail_(wc_, "%s:%u: Assertion failed! This should not be reached: %s\n", \ __FILE__, __LINE__, reason); \ } while (0) From b75f8ce2aeac11669d60ccdfb334412cb31b63c9 Mon Sep 17 00:00:00 2001 From: Leandro Ribeiro Date: Mon, 16 Mar 2026 17:38:20 -0300 Subject: [PATCH 02/16] shared: add wl_list asserts We were missing that, so let's include them. Signed-off-by: Leandro Ribeiro --- libweston/backend-drm/drm.c | 2 +- libweston/linux-dmabuf.c | 2 +- shared/weston-assert.h | 26 ++++++++++++++++++++++++++ tests/assert-test.c | 29 +++++++++++++++++++++++++++++ 4 files changed, 57 insertions(+), 2 deletions(-) diff --git a/libweston/backend-drm/drm.c b/libweston/backend-drm/drm.c index 6e52a0cda..613ebf770 100644 --- a/libweston/backend-drm/drm.c +++ b/libweston/backend-drm/drm.c @@ -4215,7 +4215,7 @@ drm_device_destroy(struct drm_device *device) &device->writeback_connector_list, link) drm_writeback_destroy(writeback); - weston_assert_true(ec, wl_list_empty(&device->drm_colorop_3x1d_lut_list)); + weston_assert_list_empty(ec, &device->drm_colorop_3x1d_lut_list); if (device->drm_event_source) wl_event_source_remove(device->drm_event_source); diff --git a/libweston/linux-dmabuf.c b/libweston/linux-dmabuf.c index c460ff58e..75b2944d0 100644 --- a/libweston/linux-dmabuf.c +++ b/libweston/linux-dmabuf.c @@ -800,7 +800,7 @@ weston_dmabuf_feedback_send_all(struct weston_compositor *compositor, { struct wl_resource *res; - weston_assert_true(compositor, !wl_list_empty(&dmabuf_feedback->resource_list)); + weston_assert_list_not_empty(compositor, &dmabuf_feedback->resource_list); wl_resource_for_each(res, &dmabuf_feedback->resource_list) weston_dmabuf_feedback_send(dmabuf_feedback, format_table, res, false); diff --git a/shared/weston-assert.h b/shared/weston-assert.h index 905f2c5de..75cb6d66b 100644 --- a/shared/weston-assert.h +++ b/shared/weston-assert.h @@ -32,6 +32,8 @@ #include #include +#include + struct weston_compositor; __attribute__((noreturn, format(printf, 2, 3))) @@ -224,6 +226,30 @@ weston_assert_fail_(const struct weston_compositor *compositor, const char *fmt, cond; \ }) +/* wl_list asserts */ + +#define weston_assert_list_empty(compositor, list) \ +({ \ + struct weston_compositor *wc_ = compositor; \ + struct wl_list *l_ = list; \ + bool cond = wl_list_empty(l_); \ + if (!cond) \ + custom_assert_fail_(wc_, "%s:%u: Assertion failed! wl_list '%s' is not empty.\n", \ + __FILE__, __LINE__, #list); \ + cond; \ +}) + +#define weston_assert_list_not_empty(compositor, list) \ +({ \ + struct weston_compositor *wc_ = compositor; \ + struct wl_list *l_ = list; \ + bool cond = !wl_list_empty(l_); \ + if (!cond) \ + custom_assert_fail_(wc_, "%s:%u: Assertion failed! wl_list '%s' is empty.\n", \ + __FILE__, __LINE__, #list); \ + cond; \ +}) + /* Misc asserts. */ #define weston_assert_not_reached(compositor, reason) \ diff --git a/tests/assert-test.c b/tests/assert-test.c index c357c6529..2a8719c88 100644 --- a/tests/assert-test.c +++ b/tests/assert-test.c @@ -110,6 +110,35 @@ TEST(asserts_boolean) return RESULT_OK; } +TEST(asserts_list) +{ + /* Unused by the macros for now, so let's just use NULL. */ + struct weston_compositor *compositor = NULL; + struct wl_list list; + struct wl_list link; + bool ret; + + wl_list_init(&list); + + ret = weston_assert_list_empty(compositor, &list); + abort_if_not(ret); + ret = weston_assert_list_not_empty(compositor, &list); + abort_if_not(ret == false); + + wl_list_insert(&list, &link); + + ret = weston_assert_list_empty(compositor, &list); + abort_if_not(ret == false); + ret = weston_assert_list_not_empty(compositor, &list); + abort_if_not(ret); + + /* If we reach that point, it's a success so reset the assert counter + * that's been incremented to check that assertions work. */ + weston_assert_counter_reset(); + + return RESULT_OK; +} + TEST(asserts_pointer) { /* Unused by the macros for now, so let's just use NULL. */ From f960fdccf76fc5778b5abebc1c2c01b477d7d98b Mon Sep 17 00:00:00 2001 From: Leandro Ribeiro Date: Fri, 19 Sep 2025 16:09:49 -0300 Subject: [PATCH 03/16] backend-drm: few cosmetic changes related to struct drm_colorop_3x1d_lut Move struct to another part of the header, change a few variable/param names and replace an useless u64 by u32. Signed-off-by: Leandro Ribeiro --- libweston/backend-drm/drm-internal.h | 27 +++++++++++---------- libweston/backend-drm/drm.c | 36 ++++++++++++++-------------- 2 files changed, 32 insertions(+), 31 deletions(-) diff --git a/libweston/backend-drm/drm-internal.h b/libweston/backend-drm/drm-internal.h index d4ba085d0..be37829f3 100644 --- a/libweston/backend-drm/drm-internal.h +++ b/libweston/backend-drm/drm-internal.h @@ -393,6 +393,20 @@ struct drm_output_state { bool planes_enabled; }; +struct drm_colorop_3x1d_lut { + /* drm_device::drm_colorop_3x1d_lut_list */ + struct wl_list link; + struct drm_device *device; + + /* Lifetime matches the xform. */ + struct weston_color_transform *xform; + struct wl_listener destroy_listener; + + uint32_t lut_len; + + uint32_t blob_id; +}; + /** * Plane state holds the dynamic state for a plane: where it is positioned, * and which buffer it is currently displaying. @@ -548,19 +562,6 @@ 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; diff --git a/libweston/backend-drm/drm.c b/libweston/backend-drm/drm.c index 613ebf770..01d0dc3ed 100644 --- a/libweston/backend-drm/drm.c +++ b/libweston/backend-drm/drm.c @@ -2254,21 +2254,21 @@ drm_colorop_3x1d_lut_destroy_handler(struct wl_listener *l, void *data) static struct drm_colorop_3x1d_lut * drm_colorop_3x1d_lut_search(struct drm_device *device, struct weston_color_transform *xform, - uint64_t lut_size) + uint32_t lut_len) { - struct drm_colorop_3x1d_lut *colorop_lut; + struct drm_colorop_3x1d_lut *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; + wl_list_for_each(lut, &device->drm_colorop_3x1d_lut_list, link) + if (lut->xform == xform && lut->lut_len == lut_len) + return 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) +drm_colorop_3x1d_lut_create(struct drm_device *device, + struct weston_color_transform *xform, + uint32_t lut_len, uint32_t blob_id) { struct drm_colorop_3x1d_lut *lut; @@ -2277,7 +2277,7 @@ drm_colorop_3x1d_lut_create(struct weston_color_transform *xform, lut->device = device; lut->blob_id = blob_id; lut->xform = xform; - lut->lut_size = lut_size; + lut->lut_len = lut_len; wl_list_insert(&device->drm_colorop_3x1d_lut_list, &lut->link); @@ -2336,7 +2336,7 @@ drm_output_pick_blend_to_output(struct drm_output *output) struct drm_colorop_3x1d_lut *colorop_lut; struct weston_color_transform *xform; struct drm_color_lut *drm_lut; - size_t lut_size; + size_t lut_len; uint32_t gamma_lut_blob_id; struct weston_vec3f *cm_lut; char *err_msg; @@ -2349,8 +2349,8 @@ drm_output_pick_blend_to_output(struct drm_output *output) if (!xform) return 0; - lut_size = output->crtc->lut_size; - if (lut_size == 0) { + lut_len = output->crtc->lut_size; + if (lut_len == 0) { drm_debug(b, "[output] can't offload blend-to-output: GAMMA_LUT_SIZE unsupported\n"); return -1; } @@ -2359,13 +2359,13 @@ drm_output_pick_blend_to_output(struct drm_output *output) * 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); + colorop_lut = drm_colorop_3x1d_lut_search(device, xform, lut_len); 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); + cm_lut = lut_3x1d_from_blend_to_output(compositor, xform, lut_len, &err_msg); if (!cm_lut) { drm_debug(b, "[output] failed to create 3x1D LUT for blend-to-output: %s\n", err_msg); @@ -2373,14 +2373,14 @@ drm_output_pick_blend_to_output(struct drm_output *output) return -1; } - drm_lut = xzalloc(lut_size * sizeof(*drm_lut)); - for (i = 0; i < lut_size; i++) { + drm_lut = xzalloc(lut_len * sizeof(*drm_lut)); + for (i = 0; i < lut_len; i++) { drm_lut[i].red = cm_lut[i].r * 0xffff; drm_lut[i].green = cm_lut[i].g * 0xffff; drm_lut[i].blue = cm_lut[i].b * 0xffff; } free(cm_lut); - ret = drmModeCreatePropertyBlob(device->kms_device->fd, drm_lut, lut_size * sizeof(*drm_lut), + ret = drmModeCreatePropertyBlob(device->kms_device->fd, drm_lut, lut_len * sizeof(*drm_lut), &gamma_lut_blob_id); free(drm_lut); if (ret < 0) { @@ -2389,7 +2389,7 @@ drm_output_pick_blend_to_output(struct drm_output *output) } output->blend_to_output_xform = - drm_colorop_3x1d_lut_create(xform, device, lut_size, + drm_colorop_3x1d_lut_create(device, xform, lut_len, gamma_lut_blob_id); return 0; } From afc70eda78a941ab42f77831c6e6fe4cc71e5e8b Mon Sep 17 00:00:00 2001 From: Leandro Ribeiro Date: Mon, 19 Jan 2026 00:31:37 -0300 Subject: [PATCH 04/16] backend-drm: rename drm_colorop_3x1d_lut to drm_colorop_3x1d_lut_blob We use this struct to cache and avoid creating multiple blobs for the same 3x1D LUT. So let's rename to better reflect that. Signed-off-by: Leandro Ribeiro --- libweston/backend-drm/drm-internal.h | 10 +++--- libweston/backend-drm/drm.c | 46 ++++++++++++++-------------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/libweston/backend-drm/drm-internal.h b/libweston/backend-drm/drm-internal.h index be37829f3..7d2600c07 100644 --- a/libweston/backend-drm/drm-internal.h +++ b/libweston/backend-drm/drm-internal.h @@ -247,8 +247,8 @@ 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_colorop_3x1d_lut_blob::link */ + struct wl_list drm_colorop_3x1d_lut_blob_list; int reused_state_failures; }; @@ -393,8 +393,8 @@ struct drm_output_state { bool planes_enabled; }; -struct drm_colorop_3x1d_lut { - /* drm_device::drm_colorop_3x1d_lut_list */ +struct drm_colorop_3x1d_lut_blob { + /* drm_device::drm_colorop_3x1d_lut_blob_list */ struct wl_list link; struct drm_device *device; @@ -644,7 +644,7 @@ struct drm_output { bool legacy_gamma_not_supported; uint16_t legacy_gamma_size; - struct drm_colorop_3x1d_lut *blend_to_output_xform; + struct drm_colorop_3x1d_lut_blob *blend_to_output_xform; /* Plane being displayed directly on the CRTC */ struct drm_plane_handle *scanout_handle; diff --git a/libweston/backend-drm/drm.c b/libweston/backend-drm/drm.c index 01d0dc3ed..71d0de518 100644 --- a/libweston/backend-drm/drm.c +++ b/libweston/backend-drm/drm.c @@ -2232,7 +2232,7 @@ drm_output_init_legacy_gamma_size(struct drm_output *output) } static void -drm_colorop_3x1d_lut_destroy(struct drm_colorop_3x1d_lut *lut) +drm_colorop_3x1d_lut_blob_destroy(struct drm_colorop_3x1d_lut_blob *lut) { wl_list_remove(&lut->destroy_listener.link); wl_list_remove(&lut->link); @@ -2241,36 +2241,36 @@ drm_colorop_3x1d_lut_destroy(struct drm_colorop_3x1d_lut *lut) } static void -drm_colorop_3x1d_lut_destroy_handler(struct wl_listener *l, void *data) +drm_colorop_3x1d_lut_blob_destroy_handler(struct wl_listener *l, void *data) { - struct drm_colorop_3x1d_lut *lut; + struct drm_colorop_3x1d_lut_blob *lut; lut = wl_container_of(l, lut, destroy_listener); assert(lut->xform == data); - drm_colorop_3x1d_lut_destroy(lut); + drm_colorop_3x1d_lut_blob_destroy(lut); } -static struct drm_colorop_3x1d_lut * -drm_colorop_3x1d_lut_search(struct drm_device *device, - struct weston_color_transform *xform, - uint32_t lut_len) +static struct drm_colorop_3x1d_lut_blob * +drm_colorop_3x1d_lut_blob_search(struct drm_device *device, + struct weston_color_transform *xform, + uint32_t lut_len) { - struct drm_colorop_3x1d_lut *lut; + struct drm_colorop_3x1d_lut_blob *lut; - wl_list_for_each(lut, &device->drm_colorop_3x1d_lut_list, link) + wl_list_for_each(lut, &device->drm_colorop_3x1d_lut_blob_list, link) if (lut->xform == xform && lut->lut_len == lut_len) return lut; return NULL; } -static struct drm_colorop_3x1d_lut * -drm_colorop_3x1d_lut_create(struct drm_device *device, - struct weston_color_transform *xform, - uint32_t lut_len, uint32_t blob_id) +static struct drm_colorop_3x1d_lut_blob * +drm_colorop_3x1d_lut_blob_create(struct drm_device *device, + struct weston_color_transform *xform, + uint32_t lut_len, uint32_t blob_id) { - struct drm_colorop_3x1d_lut *lut; + struct drm_colorop_3x1d_lut_blob *lut; lut = xzalloc(sizeof(*lut)); @@ -2279,9 +2279,9 @@ drm_colorop_3x1d_lut_create(struct drm_device *device, lut->xform = xform; lut->lut_len = lut_len; - wl_list_insert(&device->drm_colorop_3x1d_lut_list, &lut->link); + wl_list_insert(&device->drm_colorop_3x1d_lut_blob_list, &lut->link); - lut->destroy_listener.notify = drm_colorop_3x1d_lut_destroy_handler; + lut->destroy_listener.notify = drm_colorop_3x1d_lut_blob_destroy_handler; wl_signal_add(&lut->xform->destroy_signal, &lut->destroy_listener); return lut; @@ -2333,7 +2333,7 @@ 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 drm_colorop_3x1d_lut_blob *colorop_lut; struct weston_color_transform *xform; struct drm_color_lut *drm_lut; size_t lut_len; @@ -2359,7 +2359,7 @@ drm_output_pick_blend_to_output(struct drm_output *output) * 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_len); + colorop_lut = drm_colorop_3x1d_lut_blob_search(device, xform, lut_len); if (colorop_lut) { output->blend_to_output_xform = colorop_lut; return 0; @@ -2389,8 +2389,8 @@ drm_output_pick_blend_to_output(struct drm_output *output) } output->blend_to_output_xform = - drm_colorop_3x1d_lut_create(device, xform, lut_len, - gamma_lut_blob_id); + drm_colorop_3x1d_lut_blob_create(device, xform, lut_len, + gamma_lut_blob_id); return 0; } @@ -4215,7 +4215,7 @@ drm_device_destroy(struct drm_device *device) &device->writeback_connector_list, link) drm_writeback_destroy(writeback); - weston_assert_list_empty(ec, &device->drm_colorop_3x1d_lut_list); + weston_assert_list_empty(ec, &device->drm_colorop_3x1d_lut_blob_list); if (device->drm_event_source) wl_event_source_remove(device->drm_event_source); @@ -4555,7 +4555,7 @@ drm_device_create(struct drm_backend *backend, wl_list_init(&device->plane_list); create_planes(device); - wl_list_init(&device->drm_colorop_3x1d_lut_list); + wl_list_init(&device->drm_colorop_3x1d_lut_blob_list); wl_list_init(&device->writeback_connector_list); if (drm_backend_discover_connectors(device, device->kms_device->udev_device, res) < 0) { From c186e17cb2d2cc93e8b5d5df164194082e0763e2 Mon Sep 17 00:00:00 2001 From: Leandro Ribeiro Date: Tue, 29 Apr 2025 13:17:26 -0300 Subject: [PATCH 05/16] 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 --- libweston/backend-drm/colorops.c | 243 ++++++++++++++++++++++++++ libweston/backend-drm/colorops.h | 77 ++++++++ libweston/backend-drm/drm-internal.h | 9 + libweston/backend-drm/drm-kms-enums.h | 74 ++++++++ libweston/backend-drm/drm.c | 4 + libweston/backend-drm/kms.c | 73 ++++++++ libweston/backend-drm/meson.build | 4 + libweston/meson.build | 4 + 8 files changed, 488 insertions(+) create mode 100644 libweston/backend-drm/colorops.c create mode 100644 libweston/backend-drm/colorops.h diff --git a/libweston/backend-drm/colorops.c b/libweston/backend-drm/colorops.c new file mode 100644 index 000000000..5cd663bf2 --- /dev/null +++ b/libweston/backend-drm/colorops.c @@ -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; +} diff --git a/libweston/backend-drm/colorops.h b/libweston/backend-drm/colorops.h new file mode 100644 index 000000000..d3c858915 --- /dev/null +++ b/libweston/backend-drm/colorops.h @@ -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 */ diff --git a/libweston/backend-drm/drm-internal.h b/libweston/backend-drm/drm-internal.h index 7d2600c07..21a244413 100644 --- a/libweston/backend-drm/drm-internal.h +++ b/libweston/backend-drm/drm-internal.h @@ -27,6 +27,8 @@ * SOFTWARE. */ +#pragma once + #include "config.h" #include @@ -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); diff --git a/libweston/backend-drm/drm-kms-enums.h b/libweston/backend-drm/drm-kms-enums.h index ad9a11148..04e85d8b7 100644 --- a/libweston/backend-drm/drm-kms-enums.h +++ b/libweston/backend-drm/drm-kms-enums.h @@ -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 */ diff --git a/libweston/backend-drm/drm.c b/libweston/backend-drm/drm.c index 71d0de518..ad114b0ac 100644 --- a/libweston/backend-drm/drm.c +++ b/libweston/backend-drm/drm.c @@ -51,6 +51,7 @@ #include #include #include +#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); diff --git a/libweston/backend-drm/kms.c b/libweston/backend-drm/kms.c index 073f797d8..36e247480 100644 --- a/libweston/backend-drm/kms.c +++ b/libweston/backend-drm/kms.c @@ -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; diff --git a/libweston/backend-drm/meson.build b/libweston/backend-drm/meson.build index 5a5320420..210c40572 100644 --- a/libweston/backend-drm/meson.build +++ b/libweston/backend-drm/meson.build @@ -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 diff --git a/libweston/meson.build b/libweston/meson.build index b2a8cac8d..9fc9006ad 100644 --- a/libweston/meson.build +++ b/libweston/meson.build @@ -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', [ From a62be1d5829436b6e2ed7ca9fc315c3c8d980b2b Mon Sep 17 00:00:00 2001 From: Leandro Ribeiro Date: Thu, 2 Apr 2026 20:41:28 -0300 Subject: [PATCH 06/16] backend-drm: allow offloading post-blend xform only if libdrm >= 2.4.130 In commit "backend-drm: introduce color pipelines" we've started adding support for KMS plane color pipelines, which require libdrm >= 2.4.130. The final goal is using color pipelines to offload pre-blend color transformations. Currently we support offloading post-blend color transformations even with older libdrm versions, but that is not very useful by itself. This patch makes offloading post-blend color transformations dependent on the libdrm version as well. This allows keeping the code to offload pipelines in a single file (which is built only if libdrm >= 2.4.130), avoiding lots of #ifdefs and making the code easier to follow. Signed-off-by: Leandro Ribeiro --- frontend/main.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/frontend/main.c b/frontend/main.c index 02c238485..730543748 100644 --- a/frontend/main.c +++ b/frontend/main.c @@ -4076,9 +4076,13 @@ load_drm_backend(struct weston_compositor *c, int *argc, char **argv, weston_config_section_get_bool(section, "offload-blend-to-output", &offload_blend_to_output, false); - if (!c->color_manager && offload_blend_to_output) + if (!c->color_manager) offload_blend_to_output = false; +#if !CAN_OFFLOAD_COLOR_PIPELINE + offload_blend_to_output = false; +#endif + config.offload_blend_to_output = offload_blend_to_output; weston_config_section_get_string(section, From e060a434a5f748a4f52b9e011a60dc5e7b30858d Mon Sep 17 00:00:00 2001 From: Leandro Ribeiro Date: Thu, 2 Apr 2026 20:48:27 -0300 Subject: [PATCH 07/16] backend-drm: move drm_colorop_3x1d_lut_blob functions to colorop.c Follow-up of "backend-drm: offload post-blend only if libdrm >= 2.4.130". This moves code related to offloading post-blend color xform to colorops.c, which is built only if libdrm >= 2.4.130 is available. Signed-off-by: Leandro Ribeiro --- libweston/backend-drm/colorops.c | 78 ++++++++++++++++++++++++++++++++ libweston/backend-drm/colorops.h | 26 +++++++++++ libweston/backend-drm/drm.c | 56 ----------------------- 3 files changed, 104 insertions(+), 56 deletions(-) diff --git a/libweston/backend-drm/colorops.c b/libweston/backend-drm/colorops.c index 5cd663bf2..585c1c38a 100644 --- a/libweston/backend-drm/colorops.c +++ b/libweston/backend-drm/colorops.c @@ -32,6 +32,84 @@ #include "shared/weston-assert.h" #include "shared/xalloc.h" +static void +drm_colorop_3x1d_lut_blob_destroy(struct drm_colorop_3x1d_lut_blob *lut) +{ + wl_list_remove(&lut->destroy_listener.link); + wl_list_remove(&lut->link); + drmModeDestroyPropertyBlob(lut->device->kms_device->fd, lut->blob_id); + free(lut); +} + +static void +drm_colorop_3x1d_lut_blob_destroy_handler(struct wl_listener *l, void *data) +{ + struct drm_colorop_3x1d_lut_blob *lut; + + lut = wl_container_of(l, lut, destroy_listener); + assert(lut->xform == data); + + drm_colorop_3x1d_lut_blob_destroy(lut); +} + +/** + * Search for a 3x1D LUT colorop blob in a DRM device. + * + * \param device The DRM device in which we want to look for the blob. + * \param xform The xform from which the LUT comes from. + * \param lut_len How many taps each of the 1D LUT has. + */ +struct drm_colorop_3x1d_lut_blob * +drm_colorop_3x1d_lut_blob_search(struct drm_device *device, + struct weston_color_transform *xform, + uint32_t lut_len) +{ + struct drm_colorop_3x1d_lut_blob *lut; + + wl_list_for_each(lut, &device->drm_colorop_3x1d_lut_blob_list, link) + if (lut->xform == xform && lut->lut_len == lut_len) + return lut; + + return NULL; +} + +/** + * Create a 3x1D LUT colorop blob. + * + * A Weston colorop is an object associated with a step from a struct + * weston_color_transform and that can be used to program KMS color operations. + * This function creates a blob for such kind of object and cache that in the + * given DRM device, so we can avoid re-creating it. + * + * \param device The DRM device in which this colorop blob is stored. + * \param xform The xform from which the LUT comes from. This object matches its + * lifetime. + * \param lut_len The number of taps for each of the 1D LUT. + * \param blob_id The KMS blob id (associated to the DRM device). + * \return The 3x1D LUT colorop blob. + */ +struct drm_colorop_3x1d_lut_blob * +drm_colorop_3x1d_lut_blob_create(struct drm_device *device, + struct weston_color_transform *xform, + uint32_t lut_len, uint32_t blob_id) +{ + struct drm_colorop_3x1d_lut_blob *lut; + + lut = xzalloc(sizeof(*lut)); + + lut->device = device; + lut->blob_id = blob_id; + lut->xform = xform; + lut->lut_len = lut_len; + + wl_list_insert(&device->drm_colorop_3x1d_lut_blob_list, &lut->link); + + lut->destroy_listener.notify = drm_colorop_3x1d_lut_blob_destroy_handler; + wl_signal_add(&lut->xform->destroy_signal, &lut->destroy_listener); + + return lut; +} + static void drm_colorop_destroy(struct drm_colorop *colorop) { diff --git a/libweston/backend-drm/colorops.h b/libweston/backend-drm/colorops.h index d3c858915..baa5dffbf 100644 --- a/libweston/backend-drm/colorops.h +++ b/libweston/backend-drm/colorops.h @@ -54,6 +54,16 @@ struct drm_color_pipeline { #if CAN_OFFLOAD_COLOR_PIPELINE +struct drm_colorop_3x1d_lut_blob * +drm_colorop_3x1d_lut_blob_create(struct drm_device *device, + struct weston_color_transform *xform, + uint32_t lut_len, uint32_t blob_id); + +struct drm_colorop_3x1d_lut_blob * +drm_colorop_3x1d_lut_blob_search(struct drm_device *device, + struct weston_color_transform *xform, + uint32_t lut_len); + void drm_plane_populate_color_pipelines(struct drm_plane *plane, drmModeObjectPropertiesPtr plane_props); @@ -63,6 +73,22 @@ drm_plane_release_color_pipelines(struct drm_plane *plane); #else /* CAN_OFFLOAD_COLOR_PIPELINE */ +static inline struct drm_colorop_3x1d_lut_blob * +drm_colorop_3x1d_lut_blob_create(struct drm_device *device, + struct weston_color_transform *xform, + uint32_t lut_len, uint32_t blob_id) +{ + return NULL; +} + +static inline struct drm_colorop_3x1d_lut_blob * +drm_colorop_3x1d_lut_blob_search(struct drm_device *device, + struct weston_color_transform *xform, + uint32_t lut_len) +{ + return NULL; +} + static inline void drm_plane_populate_color_pipelines(struct drm_plane *plane, drmModeObjectPropertiesPtr plane_props) diff --git a/libweston/backend-drm/drm.c b/libweston/backend-drm/drm.c index ad114b0ac..e38df30cc 100644 --- a/libweston/backend-drm/drm.c +++ b/libweston/backend-drm/drm.c @@ -2235,62 +2235,6 @@ drm_output_init_legacy_gamma_size(struct drm_output *output) return 0; } -static void -drm_colorop_3x1d_lut_blob_destroy(struct drm_colorop_3x1d_lut_blob *lut) -{ - wl_list_remove(&lut->destroy_listener.link); - wl_list_remove(&lut->link); - drmModeDestroyPropertyBlob(lut->device->kms_device->fd, lut->blob_id); - free(lut); -} - -static void -drm_colorop_3x1d_lut_blob_destroy_handler(struct wl_listener *l, void *data) -{ - struct drm_colorop_3x1d_lut_blob *lut; - - lut = wl_container_of(l, lut, destroy_listener); - assert(lut->xform == data); - - drm_colorop_3x1d_lut_blob_destroy(lut); -} - -static struct drm_colorop_3x1d_lut_blob * -drm_colorop_3x1d_lut_blob_search(struct drm_device *device, - struct weston_color_transform *xform, - uint32_t lut_len) -{ - struct drm_colorop_3x1d_lut_blob *lut; - - wl_list_for_each(lut, &device->drm_colorop_3x1d_lut_blob_list, link) - if (lut->xform == xform && lut->lut_len == lut_len) - return lut; - - return NULL; -} - -static struct drm_colorop_3x1d_lut_blob * -drm_colorop_3x1d_lut_blob_create(struct drm_device *device, - struct weston_color_transform *xform, - uint32_t lut_len, uint32_t blob_id) -{ - struct drm_colorop_3x1d_lut_blob *lut; - - lut = xzalloc(sizeof(*lut)); - - lut->device = device; - lut->blob_id = blob_id; - lut->xform = xform; - lut->lut_len = lut_len; - - wl_list_insert(&device->drm_colorop_3x1d_lut_blob_list, &lut->link); - - lut->destroy_listener.notify = drm_colorop_3x1d_lut_blob_destroy_handler; - wl_signal_add(&lut->xform->destroy_signal, &lut->destroy_listener); - - return lut; -} - static struct weston_vec3f * lut_3x1d_from_blend_to_output(struct weston_compositor *compositor, struct weston_color_transform *xform, From 371c3921fa5e1706e89478c40ec748d0623d6862 Mon Sep 17 00:00:00 2001 From: Leandro Ribeiro Date: Tue, 2 Sep 2025 16:21:09 -0300 Subject: [PATCH 08/16] backend-drm: add curve_step to struct drm_colorop_3x1d_lut_blob No behavior change, this should be helpful in the next commits. Signed-off-by: Leandro Ribeiro --- libweston/backend-drm/colorops.c | 10 ++++++++-- libweston/backend-drm/colorops.h | 4 ++++ libweston/backend-drm/drm-internal.h | 3 +++ libweston/backend-drm/drm.c | 16 ++++++++++++---- 4 files changed, 27 insertions(+), 6 deletions(-) diff --git a/libweston/backend-drm/colorops.c b/libweston/backend-drm/colorops.c index 585c1c38a..dbe2a2f4d 100644 --- a/libweston/backend-drm/colorops.c +++ b/libweston/backend-drm/colorops.c @@ -57,17 +57,20 @@ drm_colorop_3x1d_lut_blob_destroy_handler(struct wl_listener *l, void *data) * * \param device The DRM device in which we want to look for the blob. * \param xform The xform from which the LUT comes from. + * \param curve_step What curve step from the xform originated the 3x1D LUT. * \param lut_len How many taps each of the 1D LUT has. */ struct drm_colorop_3x1d_lut_blob * drm_colorop_3x1d_lut_blob_search(struct drm_device *device, struct weston_color_transform *xform, + enum weston_color_curve_step curve_step, uint32_t lut_len) { struct drm_colorop_3x1d_lut_blob *lut; wl_list_for_each(lut, &device->drm_colorop_3x1d_lut_blob_list, link) - if (lut->xform == xform && lut->lut_len == lut_len) + if (lut->xform == xform && lut->curve_step == curve_step && + lut->lut_len == lut_len) return lut; return NULL; @@ -84,6 +87,7 @@ drm_colorop_3x1d_lut_blob_search(struct drm_device *device, * \param device The DRM device in which this colorop blob is stored. * \param xform The xform from which the LUT comes from. This object matches its * lifetime. + * \param curve_step What xform curve step originated the 3x1D LUT. * \param lut_len The number of taps for each of the 1D LUT. * \param blob_id The KMS blob id (associated to the DRM device). * \return The 3x1D LUT colorop blob. @@ -91,6 +95,7 @@ drm_colorop_3x1d_lut_blob_search(struct drm_device *device, struct drm_colorop_3x1d_lut_blob * drm_colorop_3x1d_lut_blob_create(struct drm_device *device, struct weston_color_transform *xform, + enum weston_color_curve_step curve_step, uint32_t lut_len, uint32_t blob_id) { struct drm_colorop_3x1d_lut_blob *lut; @@ -98,9 +103,10 @@ drm_colorop_3x1d_lut_blob_create(struct drm_device *device, lut = xzalloc(sizeof(*lut)); lut->device = device; - lut->blob_id = blob_id; lut->xform = xform; + lut->curve_step = curve_step; lut->lut_len = lut_len; + lut->blob_id = blob_id; wl_list_insert(&device->drm_colorop_3x1d_lut_blob_list, &lut->link); diff --git a/libweston/backend-drm/colorops.h b/libweston/backend-drm/colorops.h index baa5dffbf..7b7b2b8c8 100644 --- a/libweston/backend-drm/colorops.h +++ b/libweston/backend-drm/colorops.h @@ -57,11 +57,13 @@ struct drm_color_pipeline { struct drm_colorop_3x1d_lut_blob * drm_colorop_3x1d_lut_blob_create(struct drm_device *device, struct weston_color_transform *xform, + enum weston_color_curve_step curve_step, uint32_t lut_len, uint32_t blob_id); struct drm_colorop_3x1d_lut_blob * drm_colorop_3x1d_lut_blob_search(struct drm_device *device, struct weston_color_transform *xform, + enum weston_color_curve_step curve_step, uint32_t lut_len); void @@ -76,6 +78,7 @@ drm_plane_release_color_pipelines(struct drm_plane *plane); static inline struct drm_colorop_3x1d_lut_blob * drm_colorop_3x1d_lut_blob_create(struct drm_device *device, struct weston_color_transform *xform, + enum weston_color_curve_step curve_step, uint32_t lut_len, uint32_t blob_id) { return NULL; @@ -84,6 +87,7 @@ drm_colorop_3x1d_lut_blob_create(struct drm_device *device, static inline struct drm_colorop_3x1d_lut_blob * drm_colorop_3x1d_lut_blob_search(struct drm_device *device, struct weston_color_transform *xform, + enum weston_color_curve_step curve_step, uint32_t lut_len) { return NULL; diff --git a/libweston/backend-drm/drm-internal.h b/libweston/backend-drm/drm-internal.h index 21a244413..47a058bf2 100644 --- a/libweston/backend-drm/drm-internal.h +++ b/libweston/backend-drm/drm-internal.h @@ -406,6 +406,9 @@ struct drm_colorop_3x1d_lut_blob { struct weston_color_transform *xform; struct wl_listener destroy_listener; + /* Which curve of the xform the 3x1D LUT was generated from. */ + enum weston_color_curve_step curve_step; + uint32_t lut_len; uint32_t blob_id; diff --git a/libweston/backend-drm/drm.c b/libweston/backend-drm/drm.c index e38df30cc..14508eb2f 100644 --- a/libweston/backend-drm/drm.c +++ b/libweston/backend-drm/drm.c @@ -2283,6 +2283,7 @@ drm_output_pick_blend_to_output(struct drm_output *output) struct drm_backend *b = device->backend; struct drm_colorop_3x1d_lut_blob *colorop_lut; struct weston_color_transform *xform; + enum weston_color_curve_step curve_step; struct drm_color_lut *drm_lut; size_t lut_len; uint32_t gamma_lut_blob_id; @@ -2304,10 +2305,17 @@ drm_output_pick_blend_to_output(struct drm_output *output) } /** - * First let's check if the xform has already been cached. If that's the + * For now we expect blend-to-output to be composed of pre-curve only, + * so lut_3x1d_from_blend_to_output() will return a LUT it creates from + * the xform pre-curve. + */ + curve_step = WESTON_COLOR_CURVE_STEP_PRE; + + /** + * First let's check if the LUT has already been cached. If that's the * case, we make use of it. */ - colorop_lut = drm_colorop_3x1d_lut_blob_search(device, xform, lut_len); + colorop_lut = drm_colorop_3x1d_lut_blob_search(device, xform, curve_step, lut_len); if (colorop_lut) { output->blend_to_output_xform = colorop_lut; return 0; @@ -2337,8 +2345,8 @@ drm_output_pick_blend_to_output(struct drm_output *output) } output->blend_to_output_xform = - drm_colorop_3x1d_lut_blob_create(device, xform, lut_len, - gamma_lut_blob_id); + drm_colorop_3x1d_lut_blob_create(device, xform, curve_step, + lut_len, gamma_lut_blob_id); return 0; } From ea28be0cf877c9305c18f895beeba3bcf931d735 Mon Sep 17 00:00:00 2001 From: Leandro Ribeiro Date: Tue, 2 Sep 2025 18:01:55 -0300 Subject: [PATCH 09/16] backend-drm: add quantization to drm_colorop_3x1d_lut_blob We may have DRM LUT's for u32 and u16. This allow us to create both types. Should be useful in the next commit. u16 is used for legacy GAMMA_LUT, and u32 will be used for the newer colorops in the next commits. Signed-off-by: Leandro Ribeiro --- libweston/backend-drm/colorops.c | 76 ++++++++++++++++++++++++++-- libweston/backend-drm/colorops.h | 8 ++- libweston/backend-drm/drm-internal.h | 7 +++ libweston/backend-drm/drm.c | 28 ++++------ 4 files changed, 95 insertions(+), 24 deletions(-) diff --git a/libweston/backend-drm/colorops.c b/libweston/backend-drm/colorops.c index dbe2a2f4d..05f4f330a 100644 --- a/libweston/backend-drm/colorops.c +++ b/libweston/backend-drm/colorops.c @@ -58,24 +58,53 @@ drm_colorop_3x1d_lut_blob_destroy_handler(struct wl_listener *l, void *data) * \param device The DRM device in which we want to look for the blob. * \param xform The xform from which the LUT comes from. * \param curve_step What curve step from the xform originated the 3x1D LUT. + * \param quantization The colorop 3x1D LUT quantization (U32 or U16). * \param lut_len How many taps each of the 1D LUT has. */ struct drm_colorop_3x1d_lut_blob * drm_colorop_3x1d_lut_blob_search(struct drm_device *device, struct weston_color_transform *xform, enum weston_color_curve_step curve_step, + enum drm_colorop_3x1d_lut_blob_quantization quantization, uint32_t lut_len) { struct drm_colorop_3x1d_lut_blob *lut; wl_list_for_each(lut, &device->drm_colorop_3x1d_lut_blob_list, link) if (lut->xform == xform && lut->curve_step == curve_step && - lut->lut_len == lut_len) + lut->lut_len == lut_len && lut->quantization == quantization) return lut; return NULL; } +static struct drm_color_lut32 +drm_vec3f_to_u32(struct weston_vec3f vec) +{ + struct drm_color_lut32 res; + + /* UINT32_MAX exceeds the 24-bit integer precision of floats and could + * be rounded incorrectly if multiplied in float. */ + + res.red = (double) vec.r * UINT32_MAX; + res.green = (double) vec.g * UINT32_MAX; + res.blue = (double) vec.b * UINT32_MAX; + + return res; +} + +static struct drm_color_lut +drm_vec3f_to_u16(struct weston_vec3f vec) +{ + struct drm_color_lut res; + + res.red = vec.r * UINT16_MAX; + res.green = vec.g * UINT16_MAX; + res.blue = vec.b * UINT16_MAX; + + return res; +} + /** * Create a 3x1D LUT colorop blob. * @@ -88,23 +117,64 @@ drm_colorop_3x1d_lut_blob_search(struct drm_device *device, * \param xform The xform from which the LUT comes from. This object matches its * lifetime. * \param curve_step What xform curve step originated the 3x1D LUT. + * \param quantization The colorop 3x1D LUT quantization (U32 or U16). + * \param cm_lut The 3x1D LUT from which the colorop will be created. * \param lut_len The number of taps for each of the 1D LUT. - * \param blob_id The KMS blob id (associated to the DRM device). * \return The 3x1D LUT colorop blob. */ struct drm_colorop_3x1d_lut_blob * drm_colorop_3x1d_lut_blob_create(struct drm_device *device, struct weston_color_transform *xform, enum weston_color_curve_step curve_step, - uint32_t lut_len, uint32_t blob_id) + enum drm_colorop_3x1d_lut_blob_quantization quantization, + struct weston_vec3f *cm_lut, uint32_t lut_len) { + struct drm_backend *b = device->backend; struct drm_colorop_3x1d_lut_blob *lut; + uint32_t blob_id; + unsigned int i; + int ret = -1; + + switch (quantization) { + case DRM_COLOROP_3X1D_LUT_BLOB_QUANTIZATION_U16: { + struct drm_color_lut *drm_lut = + xcalloc(lut_len, sizeof(*drm_lut)); + + for (i = 0; i < lut_len; i++) + drm_lut[i] = drm_vec3f_to_u16(cm_lut[i]); + + ret = drmModeCreatePropertyBlob(device->kms_device->fd, drm_lut, lut_len * sizeof(*drm_lut), + &blob_id); + free(drm_lut); + break; + } + case DRM_COLOROP_3X1D_LUT_BLOB_QUANTIZATION_U32: { + struct drm_color_lut32 *drm_lut = + xcalloc(lut_len, sizeof(*drm_lut)); + + for (i = 0; i < lut_len; i++) + drm_lut[i] = drm_vec3f_to_u32(cm_lut[i]); + + ret = drmModeCreatePropertyBlob(device->kms_device->fd, drm_lut, lut_len * sizeof(*drm_lut), + &blob_id); + free(drm_lut); + break; + }} + + if (ret < 0) { + drm_debug(b, "[colorop] failed to create blob for colorop 3x1D LUT;\n" \ + " lut_len %u, quantization %s", + lut_len, + quantization == DRM_COLOROP_3X1D_LUT_BLOB_QUANTIZATION_U16 ? "u16" : "u32"); + return NULL; + } lut = xzalloc(sizeof(*lut)); lut->device = device; lut->xform = xform; lut->curve_step = curve_step; + lut->quantization = quantization; lut->lut_len = lut_len; lut->blob_id = blob_id; diff --git a/libweston/backend-drm/colorops.h b/libweston/backend-drm/colorops.h index 7b7b2b8c8..f95ddb2ba 100644 --- a/libweston/backend-drm/colorops.h +++ b/libweston/backend-drm/colorops.h @@ -58,12 +58,14 @@ struct drm_colorop_3x1d_lut_blob * drm_colorop_3x1d_lut_blob_create(struct drm_device *device, struct weston_color_transform *xform, enum weston_color_curve_step curve_step, - uint32_t lut_len, uint32_t blob_id); + enum drm_colorop_3x1d_lut_blob_quantization quantization, + struct weston_vec3f *cm_lut, uint32_t lut_len); struct drm_colorop_3x1d_lut_blob * drm_colorop_3x1d_lut_blob_search(struct drm_device *device, struct weston_color_transform *xform, enum weston_color_curve_step curve_step, + enum drm_colorop_3x1d_lut_blob_quantization quantization, uint32_t lut_len); void @@ -79,7 +81,8 @@ static inline struct drm_colorop_3x1d_lut_blob * drm_colorop_3x1d_lut_blob_create(struct drm_device *device, struct weston_color_transform *xform, enum weston_color_curve_step curve_step, - uint32_t lut_len, uint32_t blob_id) + enum drm_colorop_3x1d_lut_blob_quantization quantization, + struct weston_vec3f *cm_lut, uint32_t lut_len) { return NULL; } @@ -88,6 +91,7 @@ static inline struct drm_colorop_3x1d_lut_blob * drm_colorop_3x1d_lut_blob_search(struct drm_device *device, struct weston_color_transform *xform, enum weston_color_curve_step curve_step, + enum drm_colorop_3x1d_lut_blob_quantization quantization, uint32_t lut_len) { return NULL; diff --git a/libweston/backend-drm/drm-internal.h b/libweston/backend-drm/drm-internal.h index 47a058bf2..ac298c1d5 100644 --- a/libweston/backend-drm/drm-internal.h +++ b/libweston/backend-drm/drm-internal.h @@ -397,6 +397,11 @@ struct drm_output_state { bool planes_enabled; }; +enum drm_colorop_3x1d_lut_blob_quantization { + DRM_COLOROP_3X1D_LUT_BLOB_QUANTIZATION_U16 = 0, + DRM_COLOROP_3X1D_LUT_BLOB_QUANTIZATION_U32, +}; + struct drm_colorop_3x1d_lut_blob { /* drm_device::drm_colorop_3x1d_lut_blob_list */ struct wl_list link; @@ -409,6 +414,8 @@ struct drm_colorop_3x1d_lut_blob { /* Which curve of the xform the 3x1D LUT was generated from. */ enum weston_color_curve_step curve_step; + enum drm_colorop_3x1d_lut_blob_quantization quantization; + uint32_t lut_len; uint32_t blob_id; diff --git a/libweston/backend-drm/drm.c b/libweston/backend-drm/drm.c index 14508eb2f..4b44e2689 100644 --- a/libweston/backend-drm/drm.c +++ b/libweston/backend-drm/drm.c @@ -2284,13 +2284,9 @@ drm_output_pick_blend_to_output(struct drm_output *output) struct drm_colorop_3x1d_lut_blob *colorop_lut; struct weston_color_transform *xform; enum weston_color_curve_step curve_step; - struct drm_color_lut *drm_lut; size_t lut_len; - uint32_t gamma_lut_blob_id; struct weston_vec3f *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); @@ -2315,7 +2311,9 @@ drm_output_pick_blend_to_output(struct drm_output *output) * First let's check if the LUT has already been cached. If that's the * case, we make use of it. */ - colorop_lut = drm_colorop_3x1d_lut_blob_search(device, xform, curve_step, lut_len); + colorop_lut = drm_colorop_3x1d_lut_blob_search(device, xform, curve_step, + DRM_COLOROP_3X1D_LUT_BLOB_QUANTIZATION_U16, + lut_len); if (colorop_lut) { output->blend_to_output_xform = colorop_lut; return 0; @@ -2329,24 +2327,16 @@ drm_output_pick_blend_to_output(struct drm_output *output) return -1; } - drm_lut = xzalloc(lut_len * sizeof(*drm_lut)); - for (i = 0; i < lut_len; i++) { - drm_lut[i].red = cm_lut[i].r * 0xffff; - drm_lut[i].green = cm_lut[i].g * 0xffff; - drm_lut[i].blue = cm_lut[i].b * 0xffff; - } + output->blend_to_output_xform = + drm_colorop_3x1d_lut_blob_create(device, xform, curve_step, + DRM_COLOROP_3X1D_LUT_BLOB_QUANTIZATION_U16, + cm_lut, lut_len); free(cm_lut); - ret = drmModeCreatePropertyBlob(device->kms_device->fd, drm_lut, lut_len * 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"); + if (!output->blend_to_output_xform) { + drm_debug(b, "[output] failed to create colorop 3x1D LUT"); return -1; } - output->blend_to_output_xform = - drm_colorop_3x1d_lut_blob_create(device, xform, curve_step, - lut_len, gamma_lut_blob_id); return 0; } From b79aee2a2a8991d831bcdfa42b3564d6feded38b Mon Sep 17 00:00:00 2001 From: Leandro Ribeiro Date: Sun, 8 Mar 2026 21:46:53 -0300 Subject: [PATCH 10/16] color: add KMS colorop 1D curves to tf_info Signed-off-by: Leandro Ribeiro --- libweston/color-properties.c | 28 ++++++++++++++++++++++++++++ libweston/color-properties.h | 4 ++++ 2 files changed, 32 insertions(+) diff --git a/libweston/color-properties.c b/libweston/color-properties.c index 1d39c54fc..6e9913887 100644 --- a/libweston/color-properties.c +++ b/libweston/color-properties.c @@ -279,6 +279,8 @@ static const struct weston_color_tf_info color_tf_info_table[] = { .tf = WESTON_TF_BT1886, .desc = "BT.1886", .protocol_tf = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_BT1886, + .kms_colorop = WDRM_COLOROP_CURVE_1D__COUNT, + .kms_colorop_inverse = WDRM_COLOROP_CURVE_1D__COUNT, .count_parameters = 0, /** * NOTE: This is the BT.1886 special case of L_B = 0 and @@ -292,6 +294,8 @@ static const struct weston_color_tf_info color_tf_info_table[] = { .tf = WESTON_TF_GAMMA22, .desc = "assumed display gamma 2.2", .protocol_tf = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA22, + .kms_colorop = WDRM_COLOROP_CURVE_1D_GAMMA_22, + .kms_colorop_inverse = WDRM_COLOROP_CURVE_1D_GAMMA_22_INV, .count_parameters = 0, .curve_params_valid = true, .curve = POWER_LAW(2.2, true), @@ -301,6 +305,8 @@ static const struct weston_color_tf_info color_tf_info_table[] = { .tf = WESTON_TF_GAMMA28, .desc = "assumed display gamma 2.8", .protocol_tf = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_GAMMA28, + .kms_colorop = WDRM_COLOROP_CURVE_1D__COUNT, + .kms_colorop_inverse = WDRM_COLOROP_CURVE_1D__COUNT, .count_parameters = 0, .curve_params_valid = true, .curve = POWER_LAW(2.8, true), @@ -310,12 +316,16 @@ static const struct weston_color_tf_info color_tf_info_table[] = { .tf = WESTON_TF_EXT_LINEAR, .desc = "extended linear", .protocol_tf = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR, + .kms_colorop = WDRM_COLOROP_CURVE_1D__COUNT, + .kms_colorop_inverse = WDRM_COLOROP_CURVE_1D__COUNT, .count_parameters = 0, }, { .tf = WESTON_TF_SRGB, .desc = "sRGB piece-wise", .protocol_tf = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_SRGB, + .kms_colorop = WDRM_COLOROP_CURVE_1D_SRGB_EOTF, + .kms_colorop_inverse = WDRM_COLOROP_CURVE_1D_SRGB_INV_EOTF, .count_parameters = 0, .curve_params_valid = true, .curve = SRGB_PIECE_WISE(true), @@ -325,6 +335,8 @@ static const struct weston_color_tf_info color_tf_info_table[] = { .tf = WESTON_TF_EXT_SRGB, .desc = "Extended sRGB piece-wise", .protocol_tf = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_SRGB, + .kms_colorop = WDRM_COLOROP_CURVE_1D__COUNT, + .kms_colorop_inverse = WDRM_COLOROP_CURVE_1D__COUNT, .count_parameters = 0, .curve_params_valid = true, .curve = SRGB_PIECE_WISE(false), @@ -334,47 +346,63 @@ static const struct weston_color_tf_info color_tf_info_table[] = { .tf = WESTON_TF_ST240, .desc = "SMPTE ST 240", .protocol_tf = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST240, + .kms_colorop = WDRM_COLOROP_CURVE_1D__COUNT, + .kms_colorop_inverse = WDRM_COLOROP_CURVE_1D__COUNT, .count_parameters = 0, }, { .tf = WESTON_TF_ST428, .desc = "SMPTE ST 428", .protocol_tf = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST428, + .kms_colorop = WDRM_COLOROP_CURVE_1D__COUNT, + .kms_colorop_inverse = WDRM_COLOROP_CURVE_1D__COUNT, .count_parameters = 0, }, { .tf = WESTON_TF_ST2084_PQ, .desc = "Perceptual Quantizer", .protocol_tf = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_ST2084_PQ, + .kms_colorop = WDRM_COLOROP_CURVE_1D__COUNT, + .kms_colorop_inverse = WDRM_COLOROP_CURVE_1D__COUNT, .count_parameters = 0, }, { .tf = WESTON_TF_LOG_100, .desc = "logarithmic 100:1", .protocol_tf = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_LOG_100, + .kms_colorop = WDRM_COLOROP_CURVE_1D__COUNT, + .kms_colorop_inverse = WDRM_COLOROP_CURVE_1D__COUNT, .count_parameters = 0, }, { .tf = WESTON_TF_LOG_316, .desc = "logarithmic (100*Sqrt(10) : 1)", .protocol_tf = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_LOG_316, + .kms_colorop = WDRM_COLOROP_CURVE_1D__COUNT, + .kms_colorop_inverse = WDRM_COLOROP_CURVE_1D__COUNT, .count_parameters = 0, }, { .tf = WESTON_TF_XVYCC, .desc = "IEC 61966-2-4 (xvYCC)", .protocol_tf = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_XVYCC, + .kms_colorop = WDRM_COLOROP_CURVE_1D__COUNT, + .kms_colorop_inverse = WDRM_COLOROP_CURVE_1D__COUNT, .count_parameters = 0, }, { .tf = WESTON_TF_HLG, .desc = "Hybrid log-gamma", .protocol_tf = WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_HLG, + .kms_colorop = WDRM_COLOROP_CURVE_1D__COUNT, + .kms_colorop_inverse = WDRM_COLOROP_CURVE_1D__COUNT, .count_parameters = 0, }, { .tf = WESTON_TF_POWER, .desc = "power-law with custom exponent", + .kms_colorop = WDRM_COLOROP_CURVE_1D__COUNT, + .kms_colorop_inverse = WDRM_COLOROP_CURVE_1D__COUNT, .count_parameters = 1, }, }; diff --git a/libweston/color-properties.h b/libweston/color-properties.h index 9246446a5..7698242bb 100644 --- a/libweston/color-properties.h +++ b/libweston/color-properties.h @@ -110,6 +110,10 @@ struct weston_color_tf_info { /** CM&HDR protocol extension value representing the tf. */ uint32_t protocol_tf; + /** KMS 1D curve colorop value representing the tf. */ + uint32_t kms_colorop; + uint32_t kms_colorop_inverse; + /* The protocol also has support for parameterized functions, i.e. * certain known functions that clients can define passing arbitrary * parameters. */ From 22d907bc039d515b19919d045dc3c5eb483cf448 Mon Sep 17 00:00:00 2001 From: Leandro Ribeiro Date: Tue, 2 Sep 2025 20:49:28 -0300 Subject: [PATCH 11/16] backend-drm: offload pre-blend color xform Use the per-plane color pipelines to offload pre-blend Weston color transformations when possible. Signed-off-by: Leandro Ribeiro --- libweston/backend-drm/colorops.c | 605 +++++++++++++++++++++++++- libweston/backend-drm/colorops.h | 89 ++++ libweston/backend-drm/drm-internal.h | 5 + libweston/backend-drm/drm.c | 2 + libweston/backend-drm/kms.c | 185 ++++++++ libweston/backend-drm/state-helpers.c | 3 + libweston/backend-drm/state-propose.c | 28 +- 7 files changed, 914 insertions(+), 3 deletions(-) diff --git a/libweston/backend-drm/colorops.c b/libweston/backend-drm/colorops.c index 05f4f330a..21a56b236 100644 --- a/libweston/backend-drm/colorops.c +++ b/libweston/backend-drm/colorops.c @@ -186,6 +186,193 @@ drm_colorop_3x1d_lut_blob_create(struct drm_device *device, return lut; } +enum lowering_curve_policy { + LOWERING_CURVE_POLICY_ALLOW = true, + LOWERING_CURVE_POLICY_DENY = false, +}; + +static const char * +lowering_curve_policy_str(enum lowering_curve_policy policy) +{ + switch (policy) { + case LOWERING_CURVE_POLICY_DENY: + return "deny lowering curve"; + case LOWERING_CURVE_POLICY_ALLOW: + return "allow lowering curve"; + } + return "???"; +} + +static struct drm_colorop_3x1d_lut_blob * +drm_colorop_3x1d_lut_blob_from_curve(struct drm_device *device, + struct weston_color_transform *xform, + enum weston_color_curve_step curve_step, + uint32_t lut_len) +{ + struct weston_compositor *compositor = xform->cm->compositor; + struct drm_backend *b = device->backend; + struct drm_colorop_3x1d_lut_blob *colorop_lut; + char *err_msg; + struct weston_vec3f *cm_lut; + + /* No need to create, 3x1D LUT colorop already exists. */ + colorop_lut = drm_colorop_3x1d_lut_blob_search(device, xform, curve_step, + DRM_COLOROP_3X1D_LUT_BLOB_QUANTIZATION_U32, + lut_len); + if (colorop_lut) + return colorop_lut; + + cm_lut = weston_color_curve_to_3x1D_LUT(compositor, xform, curve_step, + WESTON_COLOR_PRECISION_CARELESS, + lut_len, &err_msg); + if (!cm_lut) { + drm_debug(b, "[colorop] failed to create colorop 3x1D from curve: %s\n", + err_msg); + free(err_msg); + return NULL; + } + + colorop_lut = + drm_colorop_3x1d_lut_blob_create(device, xform, curve_step, + DRM_COLOROP_3X1D_LUT_BLOB_QUANTIZATION_U32, + cm_lut, lut_len); + free(cm_lut); + if (!colorop_lut) { + drm_debug(b, "[colorop] failed to create colorop 3x1D from curve\n"); + return NULL; + } + + return colorop_lut; +} + +static void +drm_colorop_matrix_blob_destroy(struct drm_colorop_matrix_blob *mat) +{ + wl_list_remove(&mat->destroy_listener.link); + wl_list_remove(&mat->link); + drmModeDestroyPropertyBlob(mat->device->kms_device->fd, mat->blob_id); + free(mat); +} + +static void +drm_colorop_matrix_blob_destroy_handler(struct wl_listener *l, void *data) +{ + struct drm_colorop_matrix_blob *mat = + wl_container_of(l, mat, destroy_listener); + + drm_colorop_matrix_blob_destroy(mat); +} + +static struct drm_colorop_matrix_blob * +drm_colorop_matrix_blob_search(struct drm_device *device, + struct weston_color_transform *xform) +{ + struct drm_colorop_matrix_blob *mat; + + wl_list_for_each(mat, &device->drm_colorop_matrix_blob_list, link) + if (mat->xform == xform) + return mat; + + return NULL; +} + +/** + * Float to S31.32 sign-magnitude representation. + */ +static uint64_t +float_to_s31_32_sign_magnitude(float val) +{ + uint64_t ret; + + if (val < 0) { + ret = (uint64_t) (-val * (1ULL << 32)); + ret |= 1ULL << 63; + } else { + ret = (uint64_t) (val * (1ULL << 32)); + } + + return ret; +} + +static struct drm_colorop_matrix_blob * +drm_colorop_matrix_blob_create(struct drm_device *device, + struct weston_color_transform *xform, + struct drm_color_ctm_3x4 *matrix) +{ + struct drm_backend *b = device->backend; + struct drm_colorop_matrix_blob *colorop_mat; + uint32_t blob_id; + int ret; + + ret = drmModeCreatePropertyBlob(device->kms_device->fd, matrix, + sizeof(*matrix), &blob_id); + if (ret < 0) { + drm_debug(b, "[colorop] failed to create blob for matrix\n"); + return NULL; + } + + colorop_mat = xzalloc(sizeof(*colorop_mat)); + + colorop_mat->blob_id = blob_id; + colorop_mat->device = device; + colorop_mat->xform = xform; + wl_list_insert(&device->drm_colorop_matrix_blob_list, &colorop_mat->link); + colorop_mat->destroy_listener.notify = drm_colorop_matrix_blob_destroy_handler; + wl_signal_add(&xform->destroy_signal, &colorop_mat->destroy_listener); + + return colorop_mat; +} + +static struct drm_colorop_matrix_blob * +drm_colorop_matrix_blob_from_mapping(struct drm_device *device, + struct weston_color_transform *xform) +{ + struct drm_backend *b = device->backend; + struct weston_color_mapping *mapping = &xform->mapping; + struct drm_colorop_matrix_blob *colorop_mat; + struct drm_color_ctm_3x4 *mat_3x4; + unsigned int row, col; + float val; + + /* No need to create, colorop matrix already exists. */ + colorop_mat = drm_colorop_matrix_blob_search(device, xform); + if (colorop_mat) + return colorop_mat; + + mat_3x4 = xzalloc(sizeof(*mat_3x4)); + + /** + * mapping->u.mat.matrix is in column-major order. We transpose it and + * also add a new column with the offset. Also, kernel requires the + * values in S31.32 sign-magnitude representation. + */ + for (row = 0; row < 3; row++) { + for (col = 0; col < 3; col++) { + val = mapping->u.mat.matrix.col[col].el[row]; + mat_3x4->matrix[row * 4 + col] = float_to_s31_32_sign_magnitude(val); + } + val = mapping->u.mat.offset.el[row]; + mat_3x4->matrix[row * 4 + 3] = float_to_s31_32_sign_magnitude(val); + } + + colorop_mat = drm_colorop_matrix_blob_create(device, xform, mat_3x4); + free(mat_3x4); + if (!colorop_mat) { + drm_debug(b, "[colorop] failed to create colorop matrix from mapping\n"); + return NULL; + } + + return colorop_mat; +} + +static enum wdrm_colorop_curve_1d +weston_tf_to_colorop_curve(const struct weston_color_tf_info *tf_info, + enum weston_tf_direction tf_direction) +{ + return (tf_direction == WESTON_INVERSE_TF) ? + tf_info->kms_colorop_inverse : tf_info->kms_colorop; +} + static void drm_colorop_destroy(struct drm_colorop *colorop) { @@ -248,12 +435,428 @@ drm_colorop_create(struct drm_color_pipeline *pipeline, uint32_t colorop_id, return colorop; } -static const char * +/** + * Given a colorop this returns its type as a string. + * + * \param colorop The colorop. + * \return The colorop type as a string. + */ +const char * drm_colorop_type_to_str(struct drm_colorop *colorop) { return colorop->props[WDRM_COLOROP_TYPE].enum_values[colorop->type].name; } +static struct drm_colorop * +drm_colorop_iterate(struct drm_color_pipeline *pipeline, struct drm_colorop *iter) +{ + struct wl_list *list = &pipeline->colorop_list; + struct wl_list *node; + + if (iter) + node = iter->link.next; + else + node = list->next; + + if (node == list) + return NULL; + + return container_of(node, struct drm_colorop, link); +} + +static bool +is_colorop_compatible_with_curve(struct weston_compositor *compositor, + struct drm_colorop *colorop, + struct weston_color_curve *curve) +{ + struct weston_color_curve_parametric param; + struct drm_property_info *prop_info; + enum wdrm_colorop_curve_1d curve_type; + bool ret; + + if (colorop->type == WDRM_COLOROP_TYPE_1D_CURVE) { + if (curve->type != WESTON_COLOR_CURVE_TYPE_ENUM) + return false; + + curve_type = weston_tf_to_colorop_curve(curve->u.enumerated.tf.info, + curve->u.enumerated.tf_direction); + if (curve_type == WDRM_COLOROP_CURVE_1D__COUNT) + return false; + + prop_info = &colorop->props[WDRM_COLOROP_CURVE_1D]; + if (!prop_info->enum_values[curve_type].valid) + return false; + + return true; + } else if (colorop->type == WDRM_COLOROP_TYPE_1D_LUT) { + switch (curve->type) { + case WESTON_COLOR_CURVE_TYPE_LUT_3x1D: + return true; + case WESTON_COLOR_CURVE_TYPE_PARAMETRIC: + /* Parametric can be lowered to LUT. */ + return true; + case WESTON_COLOR_CURVE_TYPE_ENUM: + switch (curve->u.enumerated.tf.info->tf) { + case WESTON_TF_ST2084_PQ: + /* This TF is implemented, so we can lower curve to LUT. */ + return true; + default: + /* If we can lower the TF to parametric, we can use it + * to create a LUT. */ + ret = weston_color_curve_enum_get_parametric(compositor, + &curve->u.enumerated, + ¶m); + return ret; + } + case WESTON_COLOR_CURVE_TYPE_IDENTITY: + /* Dead code, function never called for IDENTITY. */ + weston_assert_not_reached(compositor, + "no need to get colorop for identity curve"); + } + + return false; + } + + return false; +} + +static struct drm_colorop * +search_colorop_compatible_curve(struct drm_color_pipeline *pipeline, + struct drm_colorop *previous_colorop, + struct weston_color_curve *curve, + enum lowering_curve_policy policy) +{ + struct drm_backend *b = pipeline->plane->device->backend; + struct drm_colorop *colorop = previous_colorop; + + /** + * Identity curve should not need a colorop, so calling this func for + * IDENTITY is not allowed. + */ + weston_assert_u32_ne(b->compositor, + curve->type, WESTON_COLOR_CURVE_TYPE_IDENTITY); + + while ((colorop = drm_colorop_iterate(pipeline, colorop))) { + switch (curve->type) { + case WESTON_COLOR_CURVE_TYPE_ENUM: + if (colorop->type == WDRM_COLOROP_TYPE_1D_CURVE && + is_colorop_compatible_with_curve(b->compositor, colorop, curve)) + return colorop; + else if (colorop->type == WDRM_COLOROP_TYPE_1D_LUT && + policy == LOWERING_CURVE_POLICY_ALLOW && + is_colorop_compatible_with_curve(b->compositor, colorop, curve)) + return colorop; + break; + case WESTON_COLOR_CURVE_TYPE_PARAMETRIC: + case WESTON_COLOR_CURVE_TYPE_LUT_3x1D: + if (colorop->type == WDRM_COLOROP_TYPE_1D_LUT) + return colorop; + break; + case WESTON_COLOR_CURVE_TYPE_IDENTITY: + /* Dead code. */ + weston_assert_not_reached(b->compositor, + "no need to get colorop for identity curve"); + } + + if (!colorop->can_bypass) + break; + } + + return NULL; +} + +static struct drm_colorop * +search_colorop_type(struct drm_color_pipeline *pipeline, + struct drm_colorop *previous_colorop, + enum wdrm_colorop_type type) +{ + struct drm_colorop *colorop = previous_colorop; + + while ((colorop = drm_colorop_iterate(pipeline, colorop))) { + if (colorop->type == type) + return colorop; + + if (!colorop->can_bypass) + break; + } + + return NULL; +} + +static struct drm_colorop_state * +drm_colorop_state_create(struct drm_color_pipeline_state *pipeline_state, + struct drm_colorop *colorop, + struct drm_colorop_state_object so) +{ + struct drm_colorop_state *colorop_state; + + colorop_state = xzalloc(sizeof(*colorop_state)); + + wl_list_insert(pipeline_state->colorop_state_list.prev, &colorop_state->link); + + colorop_state->colorop = colorop; + colorop_state->object = so; + + return colorop_state; +} + +static void +drm_colorop_state_destroy(struct drm_colorop_state *colorop_state) +{ + wl_list_remove(&colorop_state->link); + free(colorop_state); +} + +static struct drm_color_pipeline_state * +drm_color_pipeline_state_create(struct drm_color_pipeline *pipeline) +{ + struct drm_color_pipeline_state *state; + + state = xzalloc(sizeof(*state)); + + state->pipeline = pipeline; + + wl_list_init(&state->colorop_state_list); + + return state; +} + +/** + * Destroys a color pipeline state. + * + * @param state The pipeline state to destroy. + */ +void +drm_color_pipeline_state_destroy(struct drm_color_pipeline_state *state) +{ + struct drm_colorop_state *colorop_state, *tmp_colorop_state; + + if (!state) + return; + + wl_list_for_each_safe(colorop_state, tmp_colorop_state, + &state->colorop_state_list, link) + drm_colorop_state_destroy(colorop_state); + + free(state); +} + +static uint64_t +prop_val_from_curve(struct drm_device *device, struct drm_colorop *colorop, + struct weston_color_curve *curve) +{ + struct weston_compositor *compositor = device->backend->compositor; + enum wdrm_colorop_curve_1d curve_type; + struct drm_property_enum_info *prop_info; + + weston_assert_u32_eq(compositor, curve->type, + WESTON_COLOR_CURVE_TYPE_ENUM); + + curve_type = weston_tf_to_colorop_curve(curve->u.enumerated.tf.info, + curve->u.enumerated.tf_direction); + weston_assert_u32_ne(compositor, curve_type, + WDRM_COLOROP_CURVE_1D__COUNT); + + prop_info = &colorop->props[WDRM_COLOROP_CURVE_1D].enum_values[curve_type]; + weston_assert_true(compositor, prop_info->valid); + + return prop_info->value; +} + +static struct drm_colorop_state * +curve_create_colorop_state(struct drm_color_pipeline_state *pipeline_state, + struct drm_colorop *previous_colorop, + struct weston_color_transform *xform, + enum weston_color_curve_step curve_step, + enum lowering_curve_policy policy) +{ + struct drm_color_pipeline *pipeline = pipeline_state->pipeline; + struct weston_compositor *compositor = pipeline->plane->base.compositor; + struct drm_device *device = pipeline->plane->device; + struct drm_colorop_3x1d_lut_blob *lut_blob; + struct weston_color_curve *curve; + struct drm_colorop_state_object so = { 0 }; + struct drm_colorop *colorop; + uint32_t lut_len; + + curve = (curve_step == WESTON_COLOR_CURVE_STEP_PRE) ? &xform->pre_curve : + &xform->post_curve; + + colorop = search_colorop_compatible_curve(pipeline, previous_colorop, + curve, policy); + if (!colorop) + return NULL; + + switch (colorop->type) { + case WDRM_COLOROP_TYPE_1D_CURVE: + so.type = COLOROP_OBJECT_TYPE_CURVE; + so.curve_type_prop_val = prop_val_from_curve(device, colorop, curve); + break; + case WDRM_COLOROP_TYPE_1D_LUT: + lut_len = colorop->size; + lut_blob = drm_colorop_3x1d_lut_blob_from_curve(device, xform, + curve_step, lut_len); + if (!lut_blob) + return NULL; + so.type = COLOROP_OBJECT_TYPE_3x1D_LUT; + so.lut_3x1d_blob_id = lut_blob->blob_id; + break; + default: + weston_assert_not_reached(compositor, + "curve colorop should be 1D curve or 1D LUT"); + } + + return drm_colorop_state_create(pipeline_state, colorop, so); +} + +static struct drm_colorop_state * +mapping_create_colorop_state(struct drm_color_pipeline_state *pipeline_state, + struct drm_colorop *previous_colorop, + struct weston_color_transform *xform) +{ + struct drm_color_pipeline *pipeline = pipeline_state->pipeline; + struct weston_compositor *compositor = pipeline->plane->base.compositor; + struct drm_device *device = pipeline->plane->device; + struct weston_color_mapping *mapping = &xform->mapping; + struct drm_colorop_matrix_blob *mat_blob; + struct drm_colorop_state_object so = { 0 }; + struct drm_colorop *colorop; + + /* For now Weston has only matrices color mapping. */ + weston_assert_u32_eq(compositor, + mapping->type, WESTON_COLOR_MAPPING_TYPE_MATRIX); + + colorop = search_colorop_type(pipeline, previous_colorop, + WDRM_COLOROP_TYPE_CTM_3X4); + if (!colorop) + return NULL; + + mat_blob = drm_colorop_matrix_blob_from_mapping(device, xform); + if (!mat_blob) + return NULL; + + so.type = COLOROP_OBJECT_TYPE_MATRIX; + so.matrix_blob_id = mat_blob->blob_id; + + return drm_colorop_state_create(pipeline_state, colorop, so); +} + +static struct drm_color_pipeline_state * +drm_color_pipeline_state_from_xform_steps(struct drm_color_pipeline *pipeline, + struct weston_color_transform *xform, + enum lowering_curve_policy policy, + const char *indent) +{ + struct drm_backend *b = pipeline->plane->device->backend; + struct drm_color_pipeline_state *pipeline_state; + struct drm_colorop_state *colorop_state; + struct drm_colorop *previous_colorop; + uint32_t type; + + pipeline_state = drm_color_pipeline_state_create(pipeline); + + /* First previous_colorop: none. */ + previous_colorop = NULL; + + /* Find colorop for pre-curve. */ + type = xform->pre_curve.type; + if (type != WESTON_COLOR_CURVE_TYPE_IDENTITY) { + colorop_state = curve_create_colorop_state(pipeline_state, + previous_colorop, xform, + WESTON_COLOR_CURVE_STEP_PRE, + policy); + if (!colorop_state) + goto err; + + previous_colorop = colorop_state->colorop; + } + + /* Find colorop for color mapping. */ + type = xform->mapping.type; + if (type != WESTON_COLOR_MAPPING_TYPE_IDENTITY) { + colorop_state = mapping_create_colorop_state(pipeline_state, + previous_colorop, xform); + if (!colorop_state) + goto err; + + previous_colorop = colorop_state->colorop; + } + + /* Find colorop for post-curve. */ + type = xform->post_curve.type; + if (type != WESTON_COLOR_CURVE_TYPE_IDENTITY) { + colorop_state = curve_create_colorop_state(pipeline_state, + previous_colorop, xform, + WESTON_COLOR_CURVE_STEP_POST, + policy); + if (!colorop_state) + goto err; + } + + drm_debug(b, "%s[colorop] color pipeline id %u IS compatible with xform t%u;\n" \ + "%s policy: %s\n", + indent, pipeline->id, xform->id, indent, + lowering_curve_policy_str(policy)); + return pipeline_state; + +err: + drm_color_pipeline_state_destroy(pipeline_state); + drm_debug(b, "%s[colorop] color pipeline id %u NOT compatible with xform t%u;\n" \ + "%s policy: %s\n", + indent, pipeline->id, xform->id, indent, + lowering_curve_policy_str(policy)); + return NULL; +} + +/** + * Given a color transformation, returns a color pipeline state that can + * be used to offload such xform to KMS. + * + * @param plane The DRM plane that we plan to use to offload the view. + * @param xform The xform to offload. + * @param indent To print debug error messages with proper indentation. + * @return The color pipeline state, or NULL if no color pipelines are + * compatible with the xform. + */ +struct drm_color_pipeline_state * +drm_color_pipeline_state_from_xform(struct drm_plane *plane, + struct weston_color_transform *xform, + const char *indent) +{ + struct drm_backend *b = plane->device->backend; + struct drm_color_pipeline_state *pipeline_state; + unsigned int i, mode_index; + enum lowering_curve_policy policy; + enum lowering_curve_policy policy_modes[2] = { + LOWERING_CURVE_POLICY_DENY, LOWERING_CURVE_POLICY_ALLOW + }; + + drm_debug(b, "%s[colorop] searching color pipeline compatible with xform t%u\n", + indent, xform->id); + + /** + * Try to find a compatible pipeline. + * + * First, we try to find a compatible pipeline but not allowing Weston + * enumerated color curves to be lowered to parametric. If we can't find + * something, we start allowing that. + */ + if (xform->steps_valid) { + for (mode_index = 0; mode_index < ARRAY_LENGTH(policy_modes); mode_index++) { + policy = policy_modes[mode_index]; + for (i = 0; i < plane->num_color_pipelines; i++) { + pipeline_state = + drm_color_pipeline_state_from_xform_steps(&plane->pipelines[i], + xform, policy, indent); + if (pipeline_state) + return pipeline_state; + } + } + } + + return NULL; +} + static void drm_color_pipeline_print(struct drm_color_pipeline *pipeline, FILE *fp) { diff --git a/libweston/backend-drm/colorops.h b/libweston/backend-drm/colorops.h index f95ddb2ba..d07850ca1 100644 --- a/libweston/backend-drm/colorops.h +++ b/libweston/backend-drm/colorops.h @@ -28,6 +28,34 @@ #include "drm-internal.h" #include "drm-kms-enums.h" +struct drm_colorop_clut_blob { + /* drm_device::drm_colorop_clut_blob_list */ + struct wl_list link; + struct drm_device *device; + + /* Lifetime matches the xform. */ + struct weston_color_transform *xform; + struct wl_listener destroy_listener; + + uint32_t shaper_len; + uint32_t clut_len; + + uint32_t shaper_blob_id; + uint32_t clut_blob_id; +}; + +struct drm_colorop_matrix_blob { + /* drm_device::drm_colorop_matrix_blob_list */ + struct wl_list link; + struct drm_device *device; + + /* Lifetime matches the xform. */ + struct weston_color_transform *xform; + struct wl_listener destroy_listener; + + uint32_t blob_id; +}; + struct drm_colorop { struct drm_color_pipeline *pipeline; struct wl_list link; /* drm_pipeline::colorop_list */ @@ -46,14 +74,53 @@ struct drm_colorop { struct drm_property_info props[WDRM_COLOROP__COUNT]; }; +enum colorop_object_type { + COLOROP_OBJECT_TYPE_CURVE = 0, + COLOROP_OBJECT_TYPE_MATRIX, + COLOROP_OBJECT_TYPE_3x1D_LUT, +}; + +struct drm_colorop_state_object { + /* Defines which of the below is valid. The others are zero. */ + enum colorop_object_type type; + + uint64_t curve_type_prop_val; + uint32_t matrix_blob_id; + uint32_t lut_3x1d_blob_id; +}; + +struct drm_colorop_state { + struct drm_colorop *colorop; + /* struct drm_color_pipeline_state::colorop_state_list */ + struct wl_list link; + + /* Object that should be programmed through the colorop. */ + struct drm_colorop_state_object object; +}; + struct drm_color_pipeline { struct drm_plane *plane; struct wl_list colorop_list; /* drm_colorop::link */ uint32_t id; }; +struct drm_color_pipeline_state { + struct drm_color_pipeline *pipeline; + + /* struct drm_colorop_state::link */ + struct wl_list colorop_state_list; +}; + #if CAN_OFFLOAD_COLOR_PIPELINE +void +drm_color_pipeline_state_destroy(struct drm_color_pipeline_state *state); + +struct drm_color_pipeline_state * +drm_color_pipeline_state_from_xform(struct drm_plane *plane, + struct weston_color_transform *xform, + const char *indent); + struct drm_colorop_3x1d_lut_blob * drm_colorop_3x1d_lut_blob_create(struct drm_device *device, struct weston_color_transform *xform, @@ -68,6 +135,9 @@ drm_colorop_3x1d_lut_blob_search(struct drm_device *device, enum drm_colorop_3x1d_lut_blob_quantization quantization, uint32_t lut_len); +const char * +drm_colorop_type_to_str(struct drm_colorop *colorop); + void drm_plane_populate_color_pipelines(struct drm_plane *plane, drmModeObjectPropertiesPtr plane_props); @@ -77,6 +147,19 @@ drm_plane_release_color_pipelines(struct drm_plane *plane); #else /* CAN_OFFLOAD_COLOR_PIPELINE */ +static inline void +drm_color_pipeline_state_destroy(struct drm_color_pipeline_state *state) +{ +} + +static inline struct drm_color_pipeline_state * +drm_color_pipeline_state_from_xform(struct drm_plane *plane, + struct weston_color_transform *xform, + const char *indent) +{ + return NULL; +} + static inline struct drm_colorop_3x1d_lut_blob * drm_colorop_3x1d_lut_blob_create(struct drm_device *device, struct weston_color_transform *xform, @@ -97,6 +180,12 @@ drm_colorop_3x1d_lut_blob_search(struct drm_device *device, return NULL; } +static inline const char * +drm_colorop_type_to_str(struct drm_colorop *colorop) +{ + return "undefined"; +} + static inline void drm_plane_populate_color_pipelines(struct drm_plane *plane, drmModeObjectPropertiesPtr plane_props) diff --git a/libweston/backend-drm/drm-internal.h b/libweston/backend-drm/drm-internal.h index ac298c1d5..27c2a114b 100644 --- a/libweston/backend-drm/drm-internal.h +++ b/libweston/backend-drm/drm-internal.h @@ -253,6 +253,8 @@ struct drm_device { /* struct drm_colorop_3x1d_lut_blob::link */ struct wl_list drm_colorop_3x1d_lut_blob_list; + /* struct drm_colorop_matrix_blob::link */ + struct wl_list drm_colorop_matrix_blob_list; int reused_state_failures; }; @@ -441,6 +443,9 @@ struct drm_plane_state { struct weston_paint_node *paint_node; /**< maintained for drm_assign_planes only */ + /* only when a color transformation is being offloaded */ + struct drm_color_pipeline_state *pipeline_state; + int32_t src_x, src_y; uint32_t src_w, src_h; int32_t dest_x, dest_y; diff --git a/libweston/backend-drm/drm.c b/libweston/backend-drm/drm.c index 4b44e2689..d22d03745 100644 --- a/libweston/backend-drm/drm.c +++ b/libweston/backend-drm/drm.c @@ -4162,6 +4162,7 @@ drm_device_destroy(struct drm_device *device) drm_writeback_destroy(writeback); weston_assert_list_empty(ec, &device->drm_colorop_3x1d_lut_blob_list); + weston_assert_list_empty(ec, &device->drm_colorop_matrix_blob_list); if (device->drm_event_source) wl_event_source_remove(device->drm_event_source); @@ -4502,6 +4503,7 @@ drm_device_create(struct drm_backend *backend, create_planes(device); wl_list_init(&device->drm_colorop_3x1d_lut_blob_list); + wl_list_init(&device->drm_colorop_matrix_blob_list); wl_list_init(&device->writeback_connector_list); if (drm_backend_discover_connectors(device, device->kms_device->udev_device, res) < 0) { diff --git a/libweston/backend-drm/kms.c b/libweston/backend-drm/kms.c index 36e247480..789f7fb48 100644 --- a/libweston/backend-drm/kms.c +++ b/libweston/backend-drm/kms.c @@ -37,7 +37,10 @@ #include #include #include "shared/helpers.h" +#include "shared/string-helpers.h" +#include "shared/weston-assert.h" #include "shared/weston-drm-fourcc.h" +#include "colorops.h" #include "drm-internal.h" #include "pixel-formats.h" #include "presentation-time-server-protocol.h" @@ -1159,6 +1162,28 @@ plane_add_prop(drmModeAtomicReq *req, struct drm_plane *plane, return (ret <= 0) ? -1 : 0; } +static int +colorop_add_prop(drmModeAtomicReq *req, struct drm_colorop *colorop, + enum wdrm_colorop_property prop, uint64_t val) +{ + struct drm_plane *plane = colorop->pipeline->plane; + struct drm_device *device = plane->device; + struct drm_backend *b = device->backend; + struct drm_property_info *info = &colorop->props[prop]; + int ret; + + drm_debug(b, "\t\t\t[COLOROP:%lu] %lu (%s) -> %llu (0x%llx)\n", + (unsigned long) colorop->id, + (unsigned long) info->prop_id, info->name, + (unsigned long long) val, (unsigned long long) val); + + if (info->prop_id == 0) + return -1; + + ret = drmModeAtomicAddProperty(req, colorop->id, info->prop_id, val); + return (ret <= 0) ? -1 : 0; +} + static bool drm_connector_has_prop(struct drm_connector *connector, enum wdrm_connector_property prop) @@ -1360,6 +1385,156 @@ drm_plane_set_color_range(struct drm_plane *plane, return plane_add_prop(req, plane, WDRM_PLANE_COLOR_RANGE, color_range); } +static bool +colorop_program(drmModeAtomicReq *req, struct drm_colorop *colorop, + enum wdrm_colorop_property colorop_prop, + uint64_t prop_val, char **err_msg) +{ + int ret; + + if (colorop->can_bypass) { + ret = colorop_add_prop(req, colorop, WDRM_COLOROP_BYPASS, 0); + if (ret < 0) { + str_printf(err_msg, "failed to set colorop id %u bypass == false", + colorop->id); + return false; + } + } + + ret = colorop_add_prop(req, colorop, colorop_prop, prop_val); + if (ret < 0) { + str_printf(err_msg, "failed to program colorop id %u type %s", + colorop->id, drm_colorop_type_to_str(colorop)); + return false; + } + + return true; +} + +static bool +set_interp(drmModeAtomicReq *req, struct drm_colorop *colorop, + enum wdrm_colorop_property interp_prop, uint64_t interp_val) +{ + struct drm_property_info *info = &colorop->props[interp_prop]; + int ret; + + if (info->enum_values[interp_val].valid) { + ret = colorop_add_prop(req, colorop, interp_prop, interp_val); + if (ret >= 0) + return true; + } + + return false; +} + +static bool +drm_colorop_program(drmModeAtomicReq *req, struct drm_colorop_state *colorop_state, + const char *indent, char **err_msg) +{ + struct drm_colorop *colorop = colorop_state->colorop; + struct drm_color_pipeline *pipeline = colorop->pipeline; + struct drm_backend *b = pipeline->plane->device->backend; + struct weston_compositor *compositor = pipeline->plane->base.compositor; + enum wdrm_colorop_property colorop_prop; + uint64_t prop_val; + + switch (colorop_state->object.type) { + case COLOROP_OBJECT_TYPE_CURVE: + colorop_prop = WDRM_COLOROP_CURVE_1D; + prop_val = colorop_state->object.curve_type_prop_val; + return colorop_program(req, colorop, colorop_prop, prop_val, err_msg); + case COLOROP_OBJECT_TYPE_MATRIX: + colorop_prop = WDRM_COLOROP_DATA; + prop_val = colorop_state->object.matrix_blob_id; + return colorop_program(req, colorop, colorop_prop, prop_val, err_msg); + case COLOROP_OBJECT_TYPE_3x1D_LUT: + if (!set_interp(req, colorop, WDRM_COLOROP_LUT1D_INTERPOLATION, + WDRM_COLOROP_LUT1D_INTERPOLATION_LINEAR)) + drm_debug(b, "%s[colorop] linear LUT1D interpolation not supported or failed to set;\n" + "%susing current value set on driver\n", indent, indent); + colorop_prop = WDRM_COLOROP_DATA; + prop_val = colorop_state->object.lut_3x1d_blob_id; + return colorop_program(req, colorop, colorop_prop, prop_val, err_msg); + } + weston_assert_not_reached(compositor, + "unknown drm_colorop_state object type"); +} + +static struct drm_colorop_state * +drm_colorop_state_iter(struct drm_color_pipeline_state *pipeline_state, + struct drm_colorop_state *iter) +{ + struct wl_list *list = &pipeline_state->colorop_state_list; + struct wl_list *node; + + if (iter) + node = iter->link.next; + else + node = list->next; + + if (node == list) + return NULL; + + return container_of(node, struct drm_colorop_state, link); +} + +static int +drm_color_pipeline_program(drmModeAtomicReq *req, + struct drm_color_pipeline_state *pipeline_state, + const char *indent) +{ + struct drm_color_pipeline *pipeline = pipeline_state->pipeline; + struct drm_plane *plane = pipeline->plane; + struct drm_backend *b = plane->device->backend; + struct drm_colorop_state *colorop_state; + struct drm_colorop *colorop; + char *err_msg; + int ret_drm; + bool ret; + + drm_debug(b, "%s[PLANE:%lu] %lu (%s) -> %llu (0x%llx)\n", + indent, (unsigned long) plane->plane_id, + (unsigned long) plane->pipeline_props_id, "COLOR_PIPELINE", + (unsigned long long) pipeline_state->pipeline->id, + (unsigned long long) pipeline_state->pipeline->id); + + colorop_state = drm_colorop_state_iter(pipeline_state, + NULL /* previous colorop state (none) */); + wl_list_for_each(colorop, &pipeline->colorop_list, link) { + /* If a colorop is not in the colorop state list, bypass it. */ + if (!colorop_state || colorop != colorop_state->colorop) { + weston_assert_true(b->compositor, colorop->can_bypass); + + ret_drm = colorop_add_prop(req, colorop, WDRM_COLOROP_BYPASS, 1); + if (ret_drm >= 0) + continue; + + drm_debug(b, "%s%s[colorop] failed to set colorop id %u bypass == true", + indent, indent, colorop->id); + goto err; + } + + ret = drm_colorop_program(req, colorop_state, indent, &err_msg); + if (!ret) { + drm_debug(b, "%s%s[colorop] %s\n", indent, indent, err_msg); + free(err_msg); + goto err; + } + + colorop_state = drm_colorop_state_iter(pipeline_state, colorop_state); + } + weston_assert_ptr_null(b->compositor, colorop_state); + + /* Set plane pipeline. */ + drmModeAtomicAddProperty(req, plane->plane_id, + plane->pipeline_props_id, pipeline->id); + return 0; + +err: + drm_debug(b, "%s%s[colorop] failed to program pipeline\n", indent, indent); + return -1; +} + static int drm_output_apply_state_atomic(struct drm_output_state *state, drmModeAtomicReq *req, @@ -1521,6 +1696,16 @@ drm_output_apply_state_atomic(struct drm_output_state *state, ret |= plane_add_prop(req, plane, WDRM_PLANE_FB_DAMAGE_CLIPS, plane_state->damage_blob_id); + if (plane->props[WDRM_PLANE_COLOR_PIPELINE].prop_id != 0) { + if (plane_state->pipeline_state) { + ret |= drm_color_pipeline_program(req, plane_state->pipeline_state, + "\t\t\t"); + } else { + ret |= plane_add_prop(req, plane, + WDRM_PLANE_COLOR_PIPELINE, 0); + } + } + if (plane_state->fb && plane_state->fb->format) pinfo = plane_state->fb->format; diff --git a/libweston/backend-drm/state-helpers.c b/libweston/backend-drm/state-helpers.c index dbcef7696..dd8743246 100644 --- a/libweston/backend-drm/state-helpers.c +++ b/libweston/backend-drm/state-helpers.c @@ -32,6 +32,7 @@ #include #include +#include "colorops.h" #include "drm-internal.h" #include "shared/weston-assert.h" #include "shared/weston-drm-fourcc.h" @@ -95,6 +96,8 @@ drm_plane_state_free(struct drm_plane_state *state, bool force) state->in_fence_fd = -1; state->zpos = DRM_PLANE_ZPOS_INVALID_PLANE; state->alpha = DRM_PLANE_ALPHA_OPAQUE; + drm_color_pipeline_state_destroy(state->pipeline_state); + state->pipeline_state = NULL; /* Once the damage blob has been submitted, it is refcounted internally * by the kernel, which means we can safely discard it. diff --git a/libweston/backend-drm/state-propose.c b/libweston/backend-drm/state-propose.c index 9e4c1f19b..0babeb4f9 100644 --- a/libweston/backend-drm/state-propose.c +++ b/libweston/backend-drm/state-propose.c @@ -39,6 +39,7 @@ #include "drm-internal.h" #include "color.h" +#include "colorops.h" #include "color-representation.h" #include "linux-dmabuf.h" #include "presentation-time-server-protocol.h" @@ -141,6 +142,21 @@ drm_output_try_paint_node_on_plane(struct drm_plane_handle *handle, state->fb = drm_fb_ref(fb); state->in_fence_fd = surface->acquire_fence_fd; + drm_color_pipeline_state_destroy(state->pipeline_state); + state->pipeline_state = NULL; + if (pnode->surf_xform.transform) { + state->pipeline_state = + drm_color_pipeline_state_from_xform(plane, + pnode->surf_xform.transform, + "\t\t\t\t"); + if (!state->pipeline_state) { + drm_debug(b, "\t\t\t\t[view] not placing paint node %s on plane %lu: " + "not compatible with surface color xform\n", + pnode->internal_name, (unsigned long) plane->plane_id); + goto out; + } + } + if (fb->format && fb->format->color_model == COLOR_MODEL_YUV) { struct weston_color_representation color_rep; const struct weston_color_matrix_coef_info *matrix_coef_info; @@ -584,6 +600,14 @@ pnode_can_use_plane(struct drm_output_state *output_state, return false; } + /* If we have a surf color xform we need to be able to offload that to KMS. */ + if (pnode->surf_xform.transform && plane->num_color_pipelines == 0) { + drm_debug(b, "\t\t\t\t[plane] not trying plane %d: surf_xform present " + "but plane has no color pipelines\n", + plane->plane_id); + return false; + } + /* If the surface buffer has an in-fence fd, but the plane doesn't * support fences, we can't place the buffer on this plane. */ if (pnode->surface->acquire_fence_fd >= 0 && @@ -1371,8 +1395,8 @@ drm_output_propose_state(struct weston_output *output_base, pnode->try_view_on_plane_failure_reasons |= FAILURE_REASONS_OUTPUT_COLOR_EFFECT; - if (pnode->surf_xform.transform != NULL || - !pnode->surf_xform.identity_pipeline) + if (pnode->surf_xform.transform && (!device->color_pipeline_supported || + !pnode->output->from_blend_to_output_by_backend)) pnode->try_view_on_plane_failure_reasons |= FAILURE_REASONS_NO_COLOR_TRANSFORM; From 535dc674a96a46f53221ff8a543c18a384fb5e92 Mon Sep 17 00:00:00 2001 From: Leandro Ribeiro Date: Wed, 19 Nov 2025 23:39:10 -0300 Subject: [PATCH 12/16] gl-renderer: change len_shaper and len_lut3d to uint32_t Fix accidental use of float values for LUT's length. Signed-off-by: Leandro Ribeiro --- libweston/renderer-gl/gl-shader-config-color-transformation.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libweston/renderer-gl/gl-shader-config-color-transformation.c b/libweston/renderer-gl/gl-shader-config-color-transformation.c index b225df00a..5c0d88403 100644 --- a/libweston/renderer-gl/gl-shader-config-color-transformation.c +++ b/libweston/renderer-gl/gl-shader-config-color-transformation.c @@ -377,8 +377,8 @@ gl_renderer_color_transform_create_3dlut(struct gl_renderer *gr, struct gl_renderer_color_transform *gl_xform = NULL; float *shaper = NULL; float *lut3d = NULL; - float len_shaper; - float len_lut3d; + uint32_t len_shaper; + uint32_t len_lut3d; bool ok; /** From 85a90a49b8797233ea4912eec523bf5df0ecbef8 Mon Sep 17 00:00:00 2001 From: Leandro Ribeiro Date: Sun, 8 Feb 2026 13:21:16 -0300 Subject: [PATCH 13/16] color: rename to_shaper_plus_3dlut() to to_clut() This name is quite long and awkward to use in certain functions. On ICC nomenclature, cLUT is a multidimensional LUT with a shaper (1D LUT) in front of it. So let's just call it to_clut() to simplify. Signed-off-by: Leandro Ribeiro --- libweston/backend-drm/drm.c | 4 +- libweston/color-lcms/color-transform.c | 61 +++++++++---------- libweston/color.h | 20 +++--- .../gl-shader-config-color-transformation.c | 21 +++---- 4 files changed, 52 insertions(+), 54 deletions(-) diff --git a/libweston/backend-drm/drm.c b/libweston/backend-drm/drm.c index d22d03745..6c2e98578 100644 --- a/libweston/backend-drm/drm.c +++ b/libweston/backend-drm/drm.c @@ -2243,8 +2243,8 @@ lut_3x1d_from_blend_to_output(struct weston_compositor *compositor, /** * 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. + * use to_clut() 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"); diff --git a/libweston/color-lcms/color-transform.c b/libweston/color-lcms/color-transform.c index d3c20c048..f46f50c1f 100644 --- a/libweston/color-lcms/color-transform.c +++ b/libweston/color-lcms/color-transform.c @@ -2058,10 +2058,10 @@ cmlcms_color_transform_recipe_string(const struct cmlcms_color_transform_recipe } static bool -build_3d_lut(struct weston_compositor *compositor, - const struct cmlcms_color_transformer *transformer, - unsigned int len_shaper, const float *shaper, - unsigned int len_lut3d, float *lut3d) +build_clut(struct weston_compositor *compositor, + const struct cmlcms_color_transformer *transformer, + unsigned int len_shaper, const float *shaper, + unsigned int len_clut, float *clut) { const float *const red_curve = &shaper[0]; const float *const green_curve = &shaper[len_shaper]; @@ -2077,27 +2077,26 @@ build_3d_lut(struct weston_compositor *compositor, * Ensure the indices and byte counts cannot overflow, * and memory usage does not get ridiculous. Arbitrary limit. */ - weston_assert_u32_lt(compositor, len_lut3d, 100); + weston_assert_u32_lt(compositor, len_clut, 100); /* - * A temporary allocation that holds two 1D LUTs of length len_lut3d - * and one scratch array of vec3f of length len_lut3d. + * A temporary allocation that holds two 1D LUTs of length len_clut + * and one scratch array of vec3f of length len_clut. */ const uint32_t bytes_per_elem = 2 * sizeof (float) + sizeof *rgb_in; - tmp = malloc(len_lut3d * bytes_per_elem); + tmp = malloc(len_clut * bytes_per_elem); inverse_r = &tmp[0]; - inverse_g = &tmp[len_lut3d]; - rgb_in = (struct weston_vec3f *)&tmp[2 * len_lut3d]; - + inverse_g = &tmp[len_clut]; + rgb_in = (struct weston_vec3f *)&tmp[2 * len_clut]; /* * For each channel, use the shaper to compute the value x such that * y(x) = index / (len - 1). As the shaper is a LUT, we find the closest * neighbors of such point (x, y) and then use linear interpolation to * estimate x. */ - for (i = 0; i < len_lut3d; i++) { - float y = (float)i / (len_lut3d - 1); + for (i = 0; i < len_clut; i++) { + float y = (float)i / (len_clut - 1); inverse_r[i] = weston_inverse_evaluate_lut1d(compositor, len_shaper, red_curve, @@ -2109,9 +2108,9 @@ build_3d_lut(struct weston_compositor *compositor, } /* - * Fill in the 3D LUT: LUT(Rin, Gin, Bin) = { Rout, Gout, Bout } + * Fill in the 3D cLUT: LUT(Rin, Gin, Bin) = { Rout, Gout, Bout } * Each of Rin, Gin and Bin varies from 0.0 to 1.0. The range [0.0, 1.0] - * is evenly divided into len_lut3d number of sampling points. The + * is evenly divided into len_clut number of sampling points. The * indices of the sampling points are index_r, index_g, index_b. * * To compute { Rout, Gout, Bout }, first Rin, Gin, Bin must go through @@ -2121,29 +2120,29 @@ build_3d_lut(struct weston_compositor *compositor, * separable. * * The next step is not separable, so we iterate through all points in - * the 3D volume. The points are transformed len_lut3d points at a time + * the 3D volume. The points are transformed len_clut points at a time * (rgb_in array) to strike a balance between the number of function * calls and the memory requirements. */ - for (index_b = 0; index_b < len_lut3d; index_b++) { + for (index_b = 0; index_b < len_clut; index_b++) { float inverse_b = weston_inverse_evaluate_lut1d(compositor, len_shaper, blue_curve, - (float)index_b / (len_lut3d - 1)); - for (i = 0; i < len_lut3d; i++) + (float)index_b / (len_clut - 1)); + for (i = 0; i < len_clut; i++) rgb_in[i].b = inverse_b; - for (index_g = 0; index_g < len_lut3d; index_g++) { - for (index_r = 0; index_r < len_lut3d; index_r++) { + for (index_g = 0; index_g < len_clut; index_g++) { + for (index_r = 0; index_r < len_clut; index_r++) { rgb_in[index_r].g = inverse_g[index_g]; rgb_in[index_r].r = inverse_r[index_r]; } index_r = 0; - i = 3 * (index_r + len_lut3d * (index_g + len_lut3d * index_b)); + i = 3 * (index_r + len_clut * (index_g + len_clut * index_b)); cmlcms_color_transformer_eval(compositor, transformer, - (struct weston_vec3f *)&lut3d[i], - rgb_in, len_lut3d); + (struct weston_vec3f *)&clut[i], + rgb_in, len_clut); } } @@ -2256,16 +2255,16 @@ out: /** * Based on [1]. We get the transformer and decompose into a shaper - * (3x1D LUT) + 3D LUT. With that, we can reduce the 3D LUT dimension size + * (3x1D LUT) + 3D cLUT. With that, we can reduce the 3D LUT dimension size * without losing precision. 3D LUT dimension size is problematic because it * demands n³ memory. * * [1] https://www.littlecms.com/ASICprelinerization_CGIV08.pdf */ static bool -xform_to_shaper_plus_3dlut(struct weston_color_transform *xform_base, - uint32_t len_shaper, float *shaper, - uint32_t len_lut3d, float *lut3d) +xform_to_clut(struct weston_color_transform *xform_base, + uint32_t len_shaper, float *shaper, + uint32_t len_clut, float *clut) { struct cmlcms_color_transform *xform = to_cmlcms_xform(xform_base); struct weston_compositor *compositor = xform_base->cm->compositor; @@ -2276,8 +2275,8 @@ xform_to_shaper_plus_3dlut(struct weston_color_transform *xform_base, if (!ret) return false; - ret = build_3d_lut(compositor, &xform->transformer, - len_shaper, shaper, len_lut3d, lut3d); + ret = build_clut(compositor, &xform->transformer, + len_shaper, shaper, len_clut, clut); if (!ret) return false; @@ -2306,7 +2305,7 @@ cmlcms_color_transform_create(struct weston_color_manager_lcms *cm, xform = xzalloc(sizeof *xform); weston_color_transform_init(&xform->base, &cm->base); wl_list_init(&xform->link); - xform->base.to_shaper_plus_3dlut = xform_to_shaper_plus_3dlut; + xform->base.to_clut = xform_to_clut; cmlcms_color_transform_recipe_copy(&xform->search_key, recipe); weston_log_scope_printf(cm->transforms_scope, diff --git a/libweston/color.h b/libweston/color.h index e2afcfc47..7957119ab 100644 --- a/libweston/color.h +++ b/libweston/color.h @@ -383,8 +383,8 @@ struct weston_color_transform { /** * When this is true, users are allowed to use the steps described below * (pre curve, color mapping and post curve) and implement the color - * transformation themselves. Otherwise this is forbidden and - * to_shaper_plus_3dlut() must be used. + * transformation themselves. Otherwise this is forbidden and to_clut() + * must be used. */ bool steps_valid; @@ -403,23 +403,23 @@ struct weston_color_transform { struct weston_color_curve post_curve; /** - * Decompose the color transformation into a shaper (3x1D LUT) and a 3D - * LUT. + * Decompose the color transformation into a shaper (3x1D LUT) followed + * by a 3D cLUT. * * \param xform_base The color transformation to decompose. * \param len_shaper Number of taps in each of the 1D LUT. * \param shaper Where the shaper is saved, caller's responsibility to * allocate. - * \param len_lut3d The 3D LUT's length for each dimension. - * \param lut3d Where the 3D LUT is saved, caller's responsibility to - * allocate. Its layout on memory is: lut3d[B][G][R], i.e. R is the + * \param len_clut The 3D cLUT's length for each dimension. + * \param clut Where the 3D cLUT is saved, caller's responsibility to + * allocate. Its layout on memory is: clut[B][G][R], i.e. R is the * innermost and its index grow faster, followed by G and then B. * \return True on success, false otherwise. */ bool - (*to_shaper_plus_3dlut)(struct weston_color_transform *xform_base, - uint32_t len_shaper, float *shaper, - uint32_t len_lut3d, float *lut3d); + (*to_clut)(struct weston_color_transform *xform_base, + uint32_t len_shaper, float *shaper, + uint32_t len_clut, float *clut); }; struct weston_cvd_correction { diff --git a/libweston/renderer-gl/gl-shader-config-color-transformation.c b/libweston/renderer-gl/gl-shader-config-color-transformation.c index 5c0d88403..2c051980a 100644 --- a/libweston/renderer-gl/gl-shader-config-color-transformation.c +++ b/libweston/renderer-gl/gl-shader-config-color-transformation.c @@ -376,9 +376,9 @@ gl_renderer_color_transform_create_3dlut(struct gl_renderer *gr, { struct gl_renderer_color_transform *gl_xform = NULL; float *shaper = NULL; - float *lut3d = NULL; + float *clut = NULL; uint32_t len_shaper; - uint32_t len_lut3d; + uint32_t len_clut; bool ok; /** @@ -386,22 +386,21 @@ gl_renderer_color_transform_create_3dlut(struct gl_renderer *gr, * excessive memory consumption. */ len_shaper = 1024; - len_lut3d = 33; + len_clut = 33; shaper = zalloc(len_shaper * 3 * sizeof(*shaper)); if (!shaper) goto err; - lut3d = zalloc(3 * len_lut3d * len_lut3d * len_lut3d * sizeof(*lut3d)); - if (!lut3d) + clut = zalloc(3 * len_clut * len_clut * len_clut * sizeof(*clut)); + if (!clut) goto err; gl_xform = gl_renderer_color_transform_create(xform); if (!gl_xform) goto err; - ok = xform->to_shaper_plus_3dlut(xform, len_shaper, shaper, - len_lut3d, lut3d); + ok = xform->to_clut(xform, len_shaper, shaper, len_clut, clut); if (!ok) goto err; @@ -411,18 +410,18 @@ gl_renderer_color_transform_create_3dlut(struct gl_renderer *gr, goto err; gl_color_mapping_lut_3d_init(gr, &gl_xform->mapping, - len_lut3d, lut3d); + len_clut, clut); free(shaper); - free(lut3d); + free(clut); return gl_xform; err: if (shaper) free(shaper); - if (lut3d) - free(lut3d); + if (clut) + free(clut); if (gl_xform) gl_renderer_color_transform_destroy(gl_xform); return NULL; From 40bfedc8b8a005ae183f935530414328078f316a Mon Sep 17 00:00:00 2001 From: Leandro Ribeiro Date: Fri, 19 Sep 2025 19:29:17 -0300 Subject: [PATCH 14/16] backend-drm: decompose xform into shaper + 3D cLUT to offload to KMS When the pipelines do not support our color xform or when the color manager is unable to convert the color xform into operations that Weston understands (currently: pre-curve, color mapping and post-curve), we were simply not trying to offload the transformation. But we have an alternative: trying to decompose the xform into shaper + 3D cLUT and check if there's a compatible pipeline. This commits adds the support for that. Signed-off-by: Leandro Ribeiro --- libweston/backend-drm/colorops.c | 224 ++++++++++++++++++++++++++- libweston/backend-drm/colorops.h | 2 + libweston/backend-drm/drm-internal.h | 2 + libweston/backend-drm/drm.c | 2 + libweston/backend-drm/kms.c | 8 + 5 files changed, 235 insertions(+), 3 deletions(-) diff --git a/libweston/backend-drm/colorops.c b/libweston/backend-drm/colorops.c index 21a56b236..0e31b5442 100644 --- a/libweston/backend-drm/colorops.c +++ b/libweston/backend-drm/colorops.c @@ -25,6 +25,8 @@ #include "config.h" +#include + #include "colorops.h" #include "color-properties.h" #include "drm-internal.h" @@ -109,9 +111,10 @@ drm_vec3f_to_u16(struct weston_vec3f vec) * Create a 3x1D LUT colorop blob. * * A Weston colorop is an object associated with a step from a struct - * weston_color_transform and that can be used to program KMS color operations. - * This function creates a blob for such kind of object and cache that in the - * given DRM device, so we can avoid re-creating it. + * weston_color_transform (or the xform itself, when it gets decomposed to + * shaper + 3D LUT) and that can be used to program KMS color operations. This + * function creates a blob for such kind of object and cache that in the given + * DRM device, so we can avoid re-creating it. * * \param device The DRM device in which this colorop blob is stored. * \param xform The xform from which the LUT comes from. This object matches its @@ -245,6 +248,154 @@ drm_colorop_3x1d_lut_blob_from_curve(struct drm_device *device, return colorop_lut; } +static void +drm_colorop_clut_blob_destroy(struct drm_colorop_clut_blob *clut) +{ + wl_list_remove(&clut->destroy_listener.link); + wl_list_remove(&clut->link); + drmModeDestroyPropertyBlob(clut->device->kms_device->fd, clut->clut_blob_id); + drmModeDestroyPropertyBlob(clut->device->kms_device->fd, clut->shaper_blob_id); + free(clut); +} + +static void +drm_colorop_clut_blob_destroy_handler(struct wl_listener *l, void *data) +{ + struct drm_colorop_clut_blob *clut; + + clut = wl_container_of(l, clut, destroy_listener); + assert(clut->xform == data); + + drm_colorop_clut_blob_destroy(clut); +} + +static struct drm_colorop_clut_blob * +drm_colorop_clut_blob_create(struct drm_device *device, + struct weston_color_transform *xform, + uint32_t len_shaper, float *cm_shaper, + uint32_t len_clut, float *cm_clut) +{ + struct drm_backend *b = device->backend; + struct drm_colorop_clut_blob *clut; + struct drm_color_lut32 *drm_clut; + struct drm_color_lut32 *drm_shaper; + uint32_t clut_blob_id, shaper_blob_id; + struct weston_vec3f v; + unsigned int i, index_r, index_g, index_b, index; + int ret; + + drm_clut = xcalloc(len_clut * len_clut * len_clut, sizeof(*drm_clut)); + drm_shaper = xcalloc(len_shaper, sizeof(*drm_shaper)); + + for (i = 0; i < len_shaper; i++) { + v = WESTON_VEC3F(cm_shaper[i], + cm_shaper[i + len_shaper], + cm_shaper[i + 2 * len_shaper]); + drm_shaper[i] = drm_vec3f_to_u32(v); + } + + ret = drmModeCreatePropertyBlob(device->kms_device->fd, drm_shaper, + len_shaper * sizeof(*drm_shaper), + &shaper_blob_id); + if (ret < 0) { + drm_debug(b, "[colorop] failed to create blob for colorop shaper\n"); + goto out; + } + + /** + * Kernel uAPI doc states that the KMS 3D LUT indexes are traversed in + * BGR order (R index growing first, then G and lastly B). Our 3D cLUT + * is traversed in BGR order as well, so no index mapping is required. + */ + for (index_b = 0; index_b < len_clut; index_b++) { + for (index_g = 0; index_g < len_clut; index_g++) { + for (index_r = 0; index_r < len_clut; index_r++) { + index = index_r + len_clut * (index_g + len_clut * index_b); + v = WESTON_VEC3F(cm_clut[3 * index], + cm_clut[3 * index + 1], + cm_clut[3 * index + 2]); + drm_clut[index] = drm_vec3f_to_u32(v); + } + } + } + ret = drmModeCreatePropertyBlob(device->kms_device->fd, drm_clut, + len_clut * len_clut * len_clut * sizeof(*drm_clut), + &clut_blob_id); + if (ret < 0) { + drmModeDestroyPropertyBlob(device->kms_device->fd, shaper_blob_id); + drm_debug(b, "[colorop] failed to create blob for colorop 3D cLUT\n"); + goto out; + } + +out: + free(drm_clut); + free(drm_shaper); + + if (ret < 0) + return NULL; + + clut = xzalloc(sizeof(*clut)); + + clut->device = device; + clut->xform = xform; + clut->shaper_len = len_shaper; + clut->clut_len = len_clut; + clut->shaper_blob_id = shaper_blob_id; + clut->clut_blob_id = clut_blob_id; + + wl_list_insert(&device->drm_colorop_clut_blob_list, &clut->link); + + clut->destroy_listener.notify = drm_colorop_clut_blob_destroy_handler; + wl_signal_add(&xform->destroy_signal, &clut->destroy_listener); + + return clut; +} + +static struct drm_colorop_clut_blob * +drm_colorop_clut_blob_search(struct drm_device *device, + struct weston_color_transform *xform, + uint32_t clut_len, uint32_t shaper_len) +{ + struct drm_colorop_clut_blob *clut; + + wl_list_for_each(clut, &device->drm_colorop_clut_blob_list, link) + if (clut->xform == xform && + clut->clut_len == clut_len && clut->shaper_len == shaper_len) + return clut; + + return NULL; +} + +static struct drm_colorop_clut_blob * +drm_colorop_clut_blob_from_xform(struct drm_device *device, + struct weston_color_transform *xform, + uint32_t len_shaper, uint32_t len_clut) +{ + float *cm_shaper = NULL; + float *cm_clut = NULL; + struct drm_colorop_clut_blob *colorop_clut = NULL; + + colorop_clut = drm_colorop_clut_blob_search(device, xform, len_shaper, len_clut); + if (colorop_clut) + return colorop_clut; + + /* Get shaper + 3D cLUT from xform. */ + cm_shaper = xcalloc(3 * len_shaper, sizeof(*cm_shaper)); + cm_clut = xcalloc(3 * len_clut * len_clut * len_clut, sizeof(*cm_clut)); + if (!xform->to_clut(xform, len_shaper, cm_shaper, len_clut, cm_clut)) + goto out; + + colorop_clut = drm_colorop_clut_blob_create(device, xform, + len_shaper, cm_shaper, + len_clut, cm_clut); + +out: + free(cm_shaper); + free(cm_clut); + + return colorop_clut; +} + static void drm_colorop_matrix_blob_destroy(struct drm_colorop_matrix_blob *mat) { @@ -808,6 +959,59 @@ err: return NULL; } +static struct drm_color_pipeline_state * +drm_color_pipeline_state_from_xform_decomposed(struct drm_color_pipeline *pipeline, + struct weston_color_transform *xform, + const char *indent) +{ + struct drm_device *device = pipeline->plane->device; + struct drm_backend *b = device->backend; + struct drm_color_pipeline_state *pipeline_state = NULL; + struct drm_colorop *colorop_shaper, *colorop_clut; + struct drm_colorop_state_object so_clut = { 0 }; + struct drm_colorop_state_object so_shaper = { 0 }; + struct drm_colorop_clut_blob *clut; + + /* Find colorop for shaper (3x1D LUT). */ + colorop_shaper = search_colorop_type(pipeline, + NULL, /* previous colorop (none) */ + WDRM_COLOROP_TYPE_1D_LUT); + if (!colorop_shaper) + goto out; + + /* Find colorop for 3D cLUT. */ + colorop_clut = search_colorop_type(pipeline, + colorop_shaper, /* previous colorop */ + WDRM_COLOROP_TYPE_3D_LUT); + if (!colorop_clut) + goto out; + + clut = drm_colorop_clut_blob_from_xform(device, xform, + colorop_shaper->size, + colorop_clut->size); + if (!clut) + goto out; + + /* Create pipeline state and fill with the colorops. */ + pipeline_state = drm_color_pipeline_state_create(pipeline); + + so_shaper.type = COLOROP_OBJECT_TYPE_3x1D_LUT; + so_shaper.lut_3x1d_blob_id = clut->shaper_blob_id; + drm_colorop_state_create(pipeline_state, colorop_shaper, so_shaper); + + so_clut.type = COLOROP_OBJECT_TYPE_3D_LUT; + so_clut.lut_3d_blob_id = clut->clut_blob_id; + drm_colorop_state_create(pipeline_state, colorop_clut, so_clut); + +out: + drm_debug(b, "%s[colorop] color pipeline id %u %s compatible with xform id %u;\n" \ + "%s xform decomposed into shaper + 3D LUT\n", + indent, pipeline->id, + pipeline_state ? "IS" : "NOT", + xform->id, indent); + return pipeline_state; +} + /** * Given a color transformation, returns a color pipeline state that can * be used to offload such xform to KMS. @@ -854,6 +1058,20 @@ drm_color_pipeline_state_from_xform(struct drm_plane *plane, } } + /** + * Either the pipelines are not compatible with our xform or we were + * unable to optimize the xform to steps. Our last resource would be + * crafting a shaper + 3D LUT from the xform. Let's check if any + * pipelines would be able to handle that. + */ + for (i = 0; i < plane->num_color_pipelines; i++) { + pipeline_state = + drm_color_pipeline_state_from_xform_decomposed(&plane->pipelines[i], + xform, indent); + if (pipeline_state) + return pipeline_state; + } + return NULL; } diff --git a/libweston/backend-drm/colorops.h b/libweston/backend-drm/colorops.h index d07850ca1..fe5c9b034 100644 --- a/libweston/backend-drm/colorops.h +++ b/libweston/backend-drm/colorops.h @@ -78,6 +78,7 @@ enum colorop_object_type { COLOROP_OBJECT_TYPE_CURVE = 0, COLOROP_OBJECT_TYPE_MATRIX, COLOROP_OBJECT_TYPE_3x1D_LUT, + COLOROP_OBJECT_TYPE_3D_LUT, }; struct drm_colorop_state_object { @@ -87,6 +88,7 @@ struct drm_colorop_state_object { uint64_t curve_type_prop_val; uint32_t matrix_blob_id; uint32_t lut_3x1d_blob_id; + uint32_t lut_3d_blob_id; }; struct drm_colorop_state { diff --git a/libweston/backend-drm/drm-internal.h b/libweston/backend-drm/drm-internal.h index 27c2a114b..0671c4e1e 100644 --- a/libweston/backend-drm/drm-internal.h +++ b/libweston/backend-drm/drm-internal.h @@ -253,6 +253,8 @@ struct drm_device { /* struct drm_colorop_3x1d_lut_blob::link */ struct wl_list drm_colorop_3x1d_lut_blob_list; + /* struct drm_colorop_clut_blob::link */ + struct wl_list drm_colorop_clut_blob_list; /* struct drm_colorop_matrix_blob::link */ struct wl_list drm_colorop_matrix_blob_list; diff --git a/libweston/backend-drm/drm.c b/libweston/backend-drm/drm.c index 6c2e98578..6994d4c29 100644 --- a/libweston/backend-drm/drm.c +++ b/libweston/backend-drm/drm.c @@ -4162,6 +4162,7 @@ drm_device_destroy(struct drm_device *device) drm_writeback_destroy(writeback); weston_assert_list_empty(ec, &device->drm_colorop_3x1d_lut_blob_list); + weston_assert_list_empty(ec, &device->drm_colorop_clut_blob_list); weston_assert_list_empty(ec, &device->drm_colorop_matrix_blob_list); if (device->drm_event_source) @@ -4503,6 +4504,7 @@ drm_device_create(struct drm_backend *backend, create_planes(device); wl_list_init(&device->drm_colorop_3x1d_lut_blob_list); + wl_list_init(&device->drm_colorop_clut_blob_list); wl_list_init(&device->drm_colorop_matrix_blob_list); wl_list_init(&device->writeback_connector_list); diff --git a/libweston/backend-drm/kms.c b/libweston/backend-drm/kms.c index 789f7fb48..31d8965ad 100644 --- a/libweston/backend-drm/kms.c +++ b/libweston/backend-drm/kms.c @@ -1455,6 +1455,14 @@ drm_colorop_program(drmModeAtomicReq *req, struct drm_colorop_state *colorop_sta colorop_prop = WDRM_COLOROP_DATA; prop_val = colorop_state->object.lut_3x1d_blob_id; return colorop_program(req, colorop, colorop_prop, prop_val, err_msg); + case COLOROP_OBJECT_TYPE_3D_LUT: + if (!set_interp(req, colorop, WDRM_COLOROP_LUT3D_INTERPOLATION, + WDRM_COLOROP_LUT3D_INTERPOLATION_TETRAHEDRAL)) + drm_debug(b, "%s[colorop] tetrahedral LUT3D interpolation not supported or failed to set;\n" + "%susing current value set on driver\n", indent, indent); + colorop_prop = WDRM_COLOROP_DATA; + prop_val = colorop_state->object.lut_3d_blob_id; + return colorop_program(req, colorop, colorop_prop, prop_val, err_msg); } weston_assert_not_reached(compositor, "unknown drm_colorop_state object type"); From 2ff3ef38ffc4a4e40cafbb470bd8fcebf9288975 Mon Sep 17 00:00:00 2001 From: Leandro Ribeiro Date: Sat, 20 Sep 2025 12:00:44 -0300 Subject: [PATCH 15/16] backend-drm: offload PQ EOTF using curve colorops Up to now, we'd not try to offload WESTON_TF_ST2084_PQ through a colorop of type curve. The kernel supports only PQ 125 EOTF, which is the PQ EOTF scaled by 125. Same goes for the inverse of the EOTF. In order to support that, use multiplier colorops to scale things up or down (depending if we have EOTF or its inverse). Signed-off-by: Leandro Ribeiro --- libweston/backend-drm/colorops.c | 126 ++++++++++++++++++++++++++++--- libweston/backend-drm/colorops.h | 2 + libweston/backend-drm/kms.c | 4 + 3 files changed, 121 insertions(+), 11 deletions(-) diff --git a/libweston/backend-drm/colorops.c b/libweston/backend-drm/colorops.c index 0e31b5442..4da8a8b77 100644 --- a/libweston/backend-drm/colorops.c +++ b/libweston/backend-drm/colorops.c @@ -516,10 +516,42 @@ drm_colorop_matrix_blob_from_mapping(struct drm_device *device, return colorop_mat; } +struct colorop_curve_scaler { + float factor; + /* placement wrt the curve colorop in the chain */ + enum { + PLACEMENT_NONE = 0, + PLACEMENT_BEFORE, + PLACEMENT_AFTER, + } placement; +}; + static enum wdrm_colorop_curve_1d weston_tf_to_colorop_curve(const struct weston_color_tf_info *tf_info, - enum weston_tf_direction tf_direction) + enum weston_tf_direction tf_direction, + struct colorop_curve_scaler *scaler) { + /** + * wdrm_colorop_curve_1d only supports PQ EOTF (and its inverse) scaled + * by 125. We don't have a tf_info that corresponds to this specific + * scaled curve, but we handle it as a special case. A multiplier + * colorop is needed to scale values up or down, depending if we have + * the EOTF or its inverse. See curve_create_colorop_state(). + */ + if (tf_info->tf == WESTON_TF_ST2084_PQ) { + if (tf_direction == WESTON_INVERSE_TF) { + scaler->factor = 125.0f; + scaler->placement = PLACEMENT_BEFORE; + return WDRM_COLOROP_CURVE_1D_PQ_125_INV_EOTF; + } else { + scaler->factor = 1.0f / 125.0f; + scaler->placement = PLACEMENT_AFTER; + return WDRM_COLOROP_CURVE_1D_PQ_125_EOTF; + } + } + + scaler->factor = 1.0f; + scaler->placement = PLACEMENT_NONE; return (tf_direction == WESTON_INVERSE_TF) ? tf_info->kms_colorop_inverse : tf_info->kms_colorop; } @@ -623,6 +655,7 @@ is_colorop_compatible_with_curve(struct weston_compositor *compositor, struct weston_color_curve_parametric param; struct drm_property_info *prop_info; enum wdrm_colorop_curve_1d curve_type; + struct colorop_curve_scaler scaler; bool ret; if (colorop->type == WDRM_COLOROP_TYPE_1D_CURVE) { @@ -630,7 +663,8 @@ is_colorop_compatible_with_curve(struct weston_compositor *compositor, return false; curve_type = weston_tf_to_colorop_curve(curve->u.enumerated.tf.info, - curve->u.enumerated.tf_direction); + curve->u.enumerated.tf_direction, + &scaler); if (curve_type == WDRM_COLOROP_CURVE_1D__COUNT) return false; @@ -799,12 +833,14 @@ prop_val_from_curve(struct drm_device *device, struct drm_colorop *colorop, struct weston_compositor *compositor = device->backend->compositor; enum wdrm_colorop_curve_1d curve_type; struct drm_property_enum_info *prop_info; + struct colorop_curve_scaler scaler; weston_assert_u32_eq(compositor, curve->type, WESTON_COLOR_CURVE_TYPE_ENUM); curve_type = weston_tf_to_colorop_curve(curve->u.enumerated.tf.info, - curve->u.enumerated.tf_direction); + curve->u.enumerated.tf_direction, + &scaler); weston_assert_u32_ne(compositor, curve_type, WDRM_COLOROP_CURVE_1D__COUNT); @@ -814,6 +850,40 @@ prop_val_from_curve(struct drm_device *device, struct drm_colorop *colorop, return prop_info->value; } +static struct drm_colorop_state * +multiplier_create_colorop_state(struct drm_color_pipeline_state *pipeline_state, + struct drm_colorop *first_colorop, + struct drm_colorop *last_colorop, + float multiplier) +{ + struct drm_color_pipeline *pipeline = pipeline_state->pipeline; + struct drm_colorop_state_object so = { 0 }; + struct drm_colorop *colorop; + bool found = false; + + /** + * The multiplier colorop must be between first_colorop and + * last_colorop (excluding both). + */ + colorop = first_colorop; + while ((colorop = drm_colorop_iterate(pipeline, colorop))) { + if (colorop == last_colorop) + break; + + if (colorop->type == WDRM_COLOROP_TYPE_MULTIPLIER) { + found = true; + break; + } + } + if (!found) + return NULL; + + so.type = COLOROP_OBJECT_TYPE_MULTIPLIER; + so.multiplier = (double) multiplier * (1ULL << 32); + + return drm_colorop_state_create(pipeline_state, colorop, so); +} + static struct drm_colorop_state * curve_create_colorop_state(struct drm_color_pipeline_state *pipeline_state, struct drm_colorop *previous_colorop, @@ -827,24 +897,34 @@ curve_create_colorop_state(struct drm_color_pipeline_state *pipeline_state, struct drm_colorop_3x1d_lut_blob *lut_blob; struct weston_color_curve *curve; struct drm_colorop_state_object so = { 0 }; - struct drm_colorop *colorop; + struct drm_colorop *colorop_curve; + struct drm_colorop_state *cs_curve, *cs_multiplier; uint32_t lut_len; + struct colorop_curve_scaler scaler = (struct colorop_curve_scaler) { + .factor = 1.0f, + .placement = PLACEMENT_NONE, + }; curve = (curve_step == WESTON_COLOR_CURVE_STEP_PRE) ? &xform->pre_curve : &xform->post_curve; - colorop = search_colorop_compatible_curve(pipeline, previous_colorop, - curve, policy); - if (!colorop) + if (curve->type == WESTON_COLOR_CURVE_TYPE_ENUM) + (void) weston_tf_to_colorop_curve(curve->u.enumerated.tf.info, + curve->u.enumerated.tf_direction, + &scaler); + + colorop_curve = search_colorop_compatible_curve(pipeline, previous_colorop, + curve, policy); + if (!colorop_curve) return NULL; - switch (colorop->type) { + switch (colorop_curve->type) { case WDRM_COLOROP_TYPE_1D_CURVE: so.type = COLOROP_OBJECT_TYPE_CURVE; - so.curve_type_prop_val = prop_val_from_curve(device, colorop, curve); + so.curve_type_prop_val = prop_val_from_curve(device, colorop_curve, curve); break; case WDRM_COLOROP_TYPE_1D_LUT: - lut_len = colorop->size; + lut_len = colorop_curve->size; lut_blob = drm_colorop_3x1d_lut_blob_from_curve(device, xform, curve_step, lut_len); if (!lut_blob) @@ -857,7 +937,31 @@ curve_create_colorop_state(struct drm_color_pipeline_state *pipeline_state, "curve colorop should be 1D curve or 1D LUT"); } - return drm_colorop_state_create(pipeline_state, colorop, so); + /** + * Curve may require a multiplier colorop before or after it. + */ + + if (scaler.placement == PLACEMENT_BEFORE) + cs_multiplier = multiplier_create_colorop_state(pipeline_state, + previous_colorop, /* first colorop */ + colorop_curve, /* last colorop */ + scaler.factor); + + cs_curve = drm_colorop_state_create(pipeline_state, colorop_curve, so); + + if (scaler.placement == PLACEMENT_AFTER) + cs_multiplier = multiplier_create_colorop_state(pipeline_state, + colorop_curve, /* first colorop */ + NULL, /* last colorop */ + scaler.factor); + + if (scaler.placement != PLACEMENT_NONE && !cs_multiplier) { + drm_colorop_state_destroy(cs_curve); + return NULL; + } + + /* Return the colorop state of the colorop that comes later in the chain. */ + return (scaler.placement == PLACEMENT_AFTER) ? cs_multiplier : cs_curve; } static struct drm_colorop_state * diff --git a/libweston/backend-drm/colorops.h b/libweston/backend-drm/colorops.h index fe5c9b034..e0e7edb53 100644 --- a/libweston/backend-drm/colorops.h +++ b/libweston/backend-drm/colorops.h @@ -79,6 +79,7 @@ enum colorop_object_type { COLOROP_OBJECT_TYPE_MATRIX, COLOROP_OBJECT_TYPE_3x1D_LUT, COLOROP_OBJECT_TYPE_3D_LUT, + COLOROP_OBJECT_TYPE_MULTIPLIER, }; struct drm_colorop_state_object { @@ -89,6 +90,7 @@ struct drm_colorop_state_object { uint32_t matrix_blob_id; uint32_t lut_3x1d_blob_id; uint32_t lut_3d_blob_id; + uint64_t multiplier; }; struct drm_colorop_state { diff --git a/libweston/backend-drm/kms.c b/libweston/backend-drm/kms.c index 31d8965ad..cde357242 100644 --- a/libweston/backend-drm/kms.c +++ b/libweston/backend-drm/kms.c @@ -1463,6 +1463,10 @@ drm_colorop_program(drmModeAtomicReq *req, struct drm_colorop_state *colorop_sta colorop_prop = WDRM_COLOROP_DATA; prop_val = colorop_state->object.lut_3d_blob_id; return colorop_program(req, colorop, colorop_prop, prop_val, err_msg); + case COLOROP_OBJECT_TYPE_MULTIPLIER: + colorop_prop = WDRM_COLOROP_MULTIPLIER; + prop_val = colorop_state->object.multiplier; + return colorop_program(req, colorop, colorop_prop, prop_val, err_msg); } weston_assert_not_reached(compositor, "unknown drm_colorop_state object type"); From cc10bf6a51bcfc755d2e77334d07f6a34663d5a8 Mon Sep 17 00:00:00 2001 From: Leandro Ribeiro Date: Sat, 4 Apr 2026 02:23:27 -0300 Subject: [PATCH 16/16] color-lcms: properly map power-law curves to tf Before checking if our LittleCMS curve matches any of our known tf, we need to map it to one of our well-known LINPOW or POWLIN. We were doing that, but setting only the g param of the curve. a, b, c, and d were not being set, resulting in a mismatch when comparing the curve parameters with the ones in our tf's. As we were not finding any match, we were always mapping the LittleCMS power-law to the parametric power-law tf, even if the exponent was 2.2 or 2.4 (which have their own tf's). Now we properly set all the parameters before doing the check. Signed-off-by: Leandro Ribeiro --- libweston/color-lcms/color-transform.c | 27 +++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/libweston/color-lcms/color-transform.c b/libweston/color-lcms/color-transform.c index f46f50c1f..972dbd0f4 100644 --- a/libweston/color-lcms/color-transform.c +++ b/libweston/color-lcms/color-transform.c @@ -417,8 +417,7 @@ lcms_curve_matches_any_tf(struct weston_compositor *compositor, const float lcms_curve_params[3][MAX_PARAMS_LCMS_PARAM_CURVE]) { struct weston_color_curve_parametric curve = { 0 }; - unsigned int i, j; - uint32_t n_lcms_curve_params; + unsigned int i; curve.clamped_input = clamped_input; @@ -428,28 +427,34 @@ lcms_curve_matches_any_tf(struct weston_compositor *compositor, * LittleCMS type 1 is the pure power-law curve, which is a * special case of LINPOW. See init_curve_from_type_1(). */ - n_lcms_curve_params = 1; curve.type = WESTON_COLOR_CURVE_PARAMETRIC_TYPE_LINPOW; + for (i = 0; i < 3; i++) { + curve.params.chan[i].g = lcms_curve_params[i][0]; + /* a = 1, b = 0, c = 1, d = 0 */ + curve.params.chan[i].a = 1.0f; + curve.params.chan[i].b = 0.0f; + curve.params.chan[i].c = 1.0f; + curve.params.chan[i].d = 0.0f; + } break; case 4: /** * LittleCMS type 4 is almost exactly the same as LINPOW. See * init_curve_from_type_4(). */ - n_lcms_curve_params = 5; curve.type = WESTON_COLOR_CURVE_PARAMETRIC_TYPE_LINPOW; + for (i = 0; i < 3; i++) { + curve.params.chan[i].g = lcms_curve_params[i][0]; + curve.params.chan[i].a = lcms_curve_params[i][1]; + curve.params.chan[i].b = lcms_curve_params[i][2]; + curve.params.chan[i].c = lcms_curve_params[i][3]; + curve.params.chan[i].d = lcms_curve_params[i][4]; + } break; default: return NULL; } - weston_assert_u32_le(compositor, - n_lcms_curve_params, MAX_PARAMS_LCMS_PARAM_CURVE); - - for (i = 0; i < 3; i++) - for (j = 0; j < n_lcms_curve_params; j++) - curve.params.chan[i].data[j] = lcms_curve_params[i][j]; - return weston_color_tf_info_from_parametric_curve(&curve); }