[PATCH 08/11] drm/vkms: add support to non-uniform LUT for internal color curves
Leandro Ribeiro <[email protected]> Tue, 4 Aug 2026 17:33:48 -0300
| Newsgroups | gmane.linux.kernel,gmane.comp.video.dri.devel |
|---|---|
| Message-ID | <[email protected]> |
This extends VKMS internal LUT implementation, allowing it to represent non-uniform LUTs. Such LUTs have the X axis. LUTs that come from userspace (gamma LUT) are always uniform, so they don't have a X axis. Note: vkms_color_srgb_inv_srgb() error tolerance reduced from 1/255 to 119/65535 ~= 0.46/255. Before this patch, the test compared the results after quantizing them to 8-bit precision, while the updated test compares directly in 16-bit. The LUT precision does not change with this patch. Signed-off-by: Leandro Ribeiro <[email protected]> --- drivers/gpu/drm/vkms/tests/vkms_color_test.c | 20 +---- drivers/gpu/drm/vkms/vkms_composer.c | 85 ++++++++++++++++++-- drivers/gpu/drm/vkms/vkms_drv.h | 9 +++ drivers/gpu/drm/vkms/vkms_luts.c | 57 +++++++++++-- 4 files changed, 139 insertions(+), 32 deletions(-) diff --git a/drivers/gpu/drm/vkms/tests/vkms_color_test.c b/drivers/gpu/drm/vkms/tests/vkms_color_test.c index bb9d84377b97..571b1b579310 100644 --- a/drivers/gpu/drm/vkms/tests/vkms_color_test.c +++ b/drivers/gpu/drm/vkms/tests/vkms_color_test.c @@ -98,19 +98,6 @@ static void vkms_color_test_get_uniform_lut_index(struct kunit *test) lut_index = get_uniform_lut_index(&test_linear_lut, test_linear_array[i].red); KUNIT_EXPECT_EQ(test, drm_fixp2int_ceil(lut_index), i); } - - KUNIT_EXPECT_EQ(test, drm_fixp2int(get_uniform_lut_index(&srgb_eotf, 0x0)), 0x0); - KUNIT_EXPECT_EQ(test, drm_fixp2int_ceil(get_uniform_lut_index(&srgb_eotf, 0x0)), 0x0); - KUNIT_EXPECT_EQ(test, drm_fixp2int_ceil(get_uniform_lut_index(&srgb_eotf, 0x101)), 0x1); - KUNIT_EXPECT_EQ(test, drm_fixp2int_ceil(get_uniform_lut_index(&srgb_eotf, 0x202)), 0x2); - - KUNIT_EXPECT_EQ(test, drm_fixp2int(get_uniform_lut_index(&srgb_inv_eotf, 0x0)), 0x0); - KUNIT_EXPECT_EQ(test, drm_fixp2int_ceil(get_uniform_lut_index(&srgb_inv_eotf, 0x0)), 0x0); - KUNIT_EXPECT_EQ(test, drm_fixp2int_ceil(get_uniform_lut_index(&srgb_inv_eotf, 0x101)), 0x1); - KUNIT_EXPECT_EQ(test, drm_fixp2int_ceil(get_uniform_lut_index(&srgb_inv_eotf, 0x202)), 0x2); - - KUNIT_EXPECT_EQ(test, drm_fixp2int_ceil(get_uniform_lut_index(&srgb_eotf, 0xfefe)), 0xfe); - KUNIT_EXPECT_EQ(test, drm_fixp2int_ceil(get_uniform_lut_index(&srgb_eotf, 0xffff)), 0xff); } static void vkms_color_test_lerp(struct kunit *test) @@ -136,13 +123,14 @@ static void vkms_color_test_linear(struct kunit *test) static void vkms_color_srgb_inv_srgb(struct kunit *test) { u16 srgb, final; + u16 tolerance = 119; for (int i = 0; i < srgb_eotf.lut_length; i++) { - srgb = apply_lut_to_channel_value(&srgb_eotf, i * 0x101, LUT_RED); + srgb = apply_lut_to_channel_value(&srgb_eotf, srgb_eotf.x[i], LUT_RED); final = apply_lut_to_channel_value(&srgb_inv_eotf, srgb, LUT_RED); - KUNIT_EXPECT_GE(test, final / 0x101, i - 1); - KUNIT_EXPECT_LE(test, final / 0x101, i + 1); + KUNIT_EXPECT_GE(test, final, (int)srgb_eotf.x[i] - tolerance); + KUNIT_EXPECT_LE(test, final, (int)srgb_eotf.x[i] + tolerance); } } diff --git a/drivers/gpu/drm/vkms/vkms_composer.c b/drivers/gpu/drm/vkms/vkms_composer.c index 1a5e899e5b4a..4ae1ffd31406 100644 --- a/drivers/gpu/drm/vkms/vkms_composer.c +++ b/drivers/gpu/drm/vkms/vkms_composer.c @@ -93,27 +93,81 @@ static inline u16 lut_channel_value(const struct drm_color_lut *lut, return 0; } +static u16 apply_non_uniform_lut(const struct vkms_color_lut *lut, + u16 channel_value, + enum lut_channel channel) +{ + const struct drm_color_lut *lut_y_lo, *lut_y_hi; + u16 y_lo, y_hi; + u16 x_lo, x_hi; + unsigned int lo, hi, mid; + s64 t; + + /* + * Handle values out of LUT domain. + */ + if (channel_value <= lut->x[0]) + return lut_channel_value(&lut->y[0], channel); + if (channel_value >= lut->x[lut->lut_length - 1]) + return lut_channel_value(&lut->y[lut->lut_length - 1], channel); + + /* + * Binary search to find the largest index lo such that + * x[lo] <= channel_value. + */ + lo = 0; + hi = lut->lut_length - 1; + while (lo < hi) { + mid = lo + (hi - lo + 1) / 2; + if (lut->x[mid] <= channel_value) + lo = mid; + else + hi = mid - 1; + } + lut_y_lo = &lut->y[lo]; + + /* + * As x[0] < channel_value < x[lut_length - 1] and + * x[lo] <= channel_value, lo + 1 is a valid index. + */ + lut_y_hi = &lut->y[lo + 1]; + + x_lo = lut->x[lo]; + x_hi = lut->x[lo + 1]; + y_lo = lut_channel_value(lut_y_lo, channel); + y_hi = lut_channel_value(lut_y_hi, channel); + + /* Avoid division by zero when two consecutive x values are equal. */ + if (x_hi == x_lo) + return y_lo; + + t = drm_fixp_div(drm_int2fixp(channel_value - x_lo), + drm_int2fixp(x_hi - x_lo)); + + return lerp_u16(y_lo, y_hi, t); +} + VISIBLE_IF_KUNIT s64 get_uniform_lut_index(const struct vkms_color_lut *lut, u16 channel_value) { s64 color_channel_fp = drm_int2fixp(channel_value); + if (lut->x) { + DRM_DEBUG_DRIVER("Non-uniform LUT should not use get_uniform_lut_index()"); + return 0; + } + return drm_fixp_mul(color_channel_fp, lut->channel_value2index_ratio); } EXPORT_SYMBOL_IF_KUNIT(get_uniform_lut_index); -VISIBLE_IF_KUNIT u16 apply_lut_to_channel_value(const struct vkms_color_lut *lut, u16 channel_value, - enum lut_channel channel) +static u16 apply_uniform_lut(const struct vkms_color_lut *lut, + u16 channel_value, + enum lut_channel channel) { const struct drm_color_lut *lut_y_floor, *lut_y_ceil; s64 lut_index = get_uniform_lut_index(lut, channel_value); u16 floor_channel_value, ceil_channel_value; - /* - * This checks if `struct drm_color_lut` has any gap added by the compiler - * between the struct fields. - */ - static_assert(sizeof(struct drm_color_lut) == sizeof(__u16) * 4); - lut_y_floor = &lut->y[drm_fixp2int(lut_index)]; if (drm_fixp2int(lut_index) == (lut->lut_length - 1)) /* We're at the end of the LUT array, use same value for ceil and floor */ @@ -127,6 +181,21 @@ VISIBLE_IF_KUNIT u16 apply_lut_to_channel_value(const struct vkms_color_lut *lut return lerp_u16(floor_channel_value, ceil_channel_value, lut_index & DRM_FIXED_DECIMAL_MASK); } + +VISIBLE_IF_KUNIT u16 apply_lut_to_channel_value(const struct vkms_color_lut *lut, u16 channel_value, + enum lut_channel channel) +{ + /* + * This checks if `struct drm_color_lut` has any gap added by the compiler + * between the struct fields. + */ + static_assert(sizeof(struct drm_color_lut) == sizeof(__u16) * 4); + + if (lut->x) + return apply_non_uniform_lut(lut, channel_value, channel); + + return apply_uniform_lut(lut, channel_value, channel); +} EXPORT_SYMBOL_IF_KUNIT(apply_lut_to_channel_value); diff --git a/drivers/gpu/drm/vkms/vkms_drv.h b/drivers/gpu/drm/vkms/vkms_drv.h index 55a3ea184e44..be81844dfd22 100644 --- a/drivers/gpu/drm/vkms/vkms_drv.h +++ b/drivers/gpu/drm/vkms/vkms_drv.h @@ -158,7 +158,16 @@ struct vkms_plane { struct drm_plane base; }; +/** + * struct vkms_color_lut - Driver specific color LUT representation + * @x: LUT x-values, must be non-decreasing and may be non-uniformly spaced. + * Only required for non-uniform LUTs. + * @y: LUT y-values. + * @lut_length: The LUT length. + * @channel_value2index_ratio: helper for uniform LUTs (no x-values). + */ struct vkms_color_lut { + u16 *x; struct drm_color_lut *y; size_t lut_length; s64 channel_value2index_ratio; diff --git a/drivers/gpu/drm/vkms/vkms_luts.c b/drivers/gpu/drm/vkms/vkms_luts.c index 7b0c8eaf83b8..c2d5f9e502ae 100644 --- a/drivers/gpu/drm/vkms/vkms_luts.c +++ b/drivers/gpu/drm/vkms/vkms_luts.c @@ -12,7 +12,42 @@ * https://gitlab.freedesktop.org/hwentland/lutgen */ -static struct drm_color_lut srgb_array[] = { +static u16 linear_x[] = { + 0x0000, 0x0101, 0x0202, 0x0303, 0x0404, 0x0505, 0x0606, 0x0707, + 0x0808, 0x0909, 0x0a0a, 0x0b0b, 0x0c0c, 0x0d0d, 0x0e0e, 0x0f0f, + 0x1010, 0x1111, 0x1212, 0x1313, 0x1414, 0x1515, 0x1616, 0x1717, + 0x1818, 0x1919, 0x1a1a, 0x1b1b, 0x1c1c, 0x1d1d, 0x1e1e, 0x1f1f, + 0x2020, 0x2121, 0x2222, 0x2323, 0x2424, 0x2525, 0x2626, 0x2727, + 0x2828, 0x2929, 0x2a2a, 0x2b2b, 0x2c2c, 0x2d2d, 0x2e2e, 0x2f2f, + 0x3030, 0x3131, 0x3232, 0x3333, 0x3434, 0x3535, 0x3636, 0x3737, + 0x3838, 0x3939, 0x3a3a, 0x3b3b, 0x3c3c, 0x3d3d, 0x3e3e, 0x3f3f, + 0x4040, 0x4141, 0x4242, 0x4343, 0x4444, 0x4545, 0x4646, 0x4747, + 0x4848, 0x4949, 0x4a4a, 0x4b4b, 0x4c4c, 0x4d4d, 0x4e4e, 0x4f4f, + 0x5050, 0x5151, 0x5252, 0x5353, 0x5454, 0x5555, 0x5656, 0x5757, + 0x5858, 0x5959, 0x5a5a, 0x5b5b, 0x5c5c, 0x5d5d, 0x5e5e, 0x5f5f, + 0x6060, 0x6161, 0x6262, 0x6363, 0x6464, 0x6565, 0x6666, 0x6767, + 0x6868, 0x6969, 0x6a6a, 0x6b6b, 0x6c6c, 0x6d6d, 0x6e6e, 0x6f6f, + 0x7070, 0x7171, 0x7272, 0x7373, 0x7474, 0x7575, 0x7676, 0x7777, + 0x7878, 0x7979, 0x7a7a, 0x7b7b, 0x7c7c, 0x7d7d, 0x7e7e, 0x7f7f, + 0x8080, 0x8181, 0x8282, 0x8383, 0x8484, 0x8585, 0x8686, 0x8787, + 0x8888, 0x8989, 0x8a8a, 0x8b8b, 0x8c8c, 0x8d8d, 0x8e8e, 0x8f8f, + 0x9090, 0x9191, 0x9292, 0x9393, 0x9494, 0x9595, 0x9696, 0x9797, + 0x9898, 0x9999, 0x9a9a, 0x9b9b, 0x9c9c, 0x9d9d, 0x9e9e, 0x9f9f, + 0xa0a0, 0xa1a1, 0xa2a2, 0xa3a3, 0xa4a4, 0xa5a5, 0xa6a6, 0xa7a7, + 0xa8a8, 0xa9a9, 0xaaaa, 0xabab, 0xacac, 0xadad, 0xaeae, 0xafaf, + 0xb0b0, 0xb1b1, 0xb2b2, 0xb3b3, 0xb4b4, 0xb5b5, 0xb6b6, 0xb7b7, + 0xb8b8, 0xb9b9, 0xbaba, 0xbbbb, 0xbcbc, 0xbdbd, 0xbebe, 0xbfbf, + 0xc0c0, 0xc1c1, 0xc2c2, 0xc3c3, 0xc4c4, 0xc5c5, 0xc6c6, 0xc7c7, + 0xc8c8, 0xc9c9, 0xcaca, 0xcbcb, 0xcccc, 0xcdcd, 0xcece, 0xcfcf, + 0xd0d0, 0xd1d1, 0xd2d2, 0xd3d3, 0xd4d4, 0xd5d5, 0xd6d6, 0xd7d7, + 0xd8d8, 0xd9d9, 0xdada, 0xdbdb, 0xdcdc, 0xdddd, 0xdede, 0xdfdf, + 0xe0e0, 0xe1e1, 0xe2e2, 0xe3e3, 0xe4e4, 0xe5e5, 0xe6e6, 0xe7e7, + 0xe8e8, 0xe9e9, 0xeaea, 0xebeb, 0xecec, 0xeded, 0xeeee, 0xefef, + 0xf0f0, 0xf1f1, 0xf2f2, 0xf3f3, 0xf4f4, 0xf5f5, 0xf6f6, 0xf7f7, + 0xf8f8, 0xf9f9, 0xfafa, 0xfbfb, 0xfcfc, 0xfdfd, 0xfefe, 0xffff +}; + +static struct drm_color_lut srgb_y[] = { { 0x0, 0x0, 0x0, 0 }, { 0x13, 0x13, 0x13, 0 }, { 0x27, 0x27, 0x27, 0 }, @@ -271,14 +306,17 @@ static struct drm_color_lut srgb_array[] = { { 0xffff, 0xffff, 0xffff, 0 }, }; +static_assert(ARRAY_SIZE(linear_x) == ARRAY_SIZE(srgb_y), + "srgb x and y must have the same number of entries"); + const struct vkms_color_lut srgb_eotf = { - .y = srgb_array, - .lut_length = ARRAY_SIZE(srgb_array), - .channel_value2index_ratio = 0xff00ffll + .x = linear_x, + .y = srgb_y, + .lut_length = ARRAY_SIZE(srgb_y) }; EXPORT_SYMBOL(srgb_eotf); -static struct drm_color_lut srgb_inv_array[] = { +static struct drm_color_lut srgb_inv_y[] = { { 0x0, 0x0, 0x0, 0 }, { 0xcc2, 0xcc2, 0xcc2, 0 }, { 0x15be, 0x15be, 0x15be, 0 }, @@ -537,9 +575,12 @@ static struct drm_color_lut srgb_inv_array[] = { { 0xffff, 0xffff, 0xffff, 0 }, }; +static_assert(ARRAY_SIZE(linear_x) == ARRAY_SIZE(srgb_inv_y), + "srgb_inv x and y must have the same number of entries"); + const struct vkms_color_lut srgb_inv_eotf = { - .y = srgb_inv_array, - .lut_length = ARRAY_SIZE(srgb_inv_array), - .channel_value2index_ratio = 0xff00ffll + .x = linear_x, + .y = srgb_inv_y, + .lut_length = ARRAY_SIZE(srgb_inv_y) }; EXPORT_SYMBOL(srgb_inv_eotf); -- 2.55.0