diff --git a/include/render/color.h b/include/render/color.h index a3ab73d83..a797881bf 100644 --- a/include/render/color.h +++ b/include/render/color.h @@ -8,6 +8,7 @@ enum wlr_color_transform_type { COLOR_TRANSFORM_SRGB, COLOR_TRANSFORM_LCMS2, + COLOR_TRANSFORM_LUT_3X1D, }; struct wlr_color_transform { @@ -20,6 +21,21 @@ struct wlr_color_transform { void wlr_color_transform_init(struct wlr_color_transform *tr, enum wlr_color_transform_type type); +/** + * The formula is approximated via three 1D look-up tables. The flat lut_3x1d + * array has a length of 3 * dim. + * + * The offset of a color value for a given channel and color index is: + * + * offset = channel_index * dim + color_index + */ +struct wlr_color_transform_lut_3x1d { + struct wlr_color_transform base; + + uint16_t *lut_3x1d; + size_t dim; +}; + /** * Get a struct wlr_color_transform_lcms2 from a generic struct wlr_color_transform. * Asserts that the base type is COLOR_TRANSFORM_LCMS2. @@ -35,6 +51,20 @@ void color_transform_lcms2_finish(struct wlr_color_transform_lcms2 *tr); void color_transform_lcms2_eval(struct wlr_color_transform_lcms2 *tr, float out[static 3], const float in[static 3]); +/** + * Get a struct wlr_color_transform_lut_3x1d from a generic + * struct wlr_color_transform. Asserts that the base type is + * COLOR_TRANSFORM_LUT_3X1D. + */ +struct wlr_color_transform_lut_3x1d *color_transform_lut_3x1d_from_base( + struct wlr_color_transform *tr); + +/** + * Evaluate a 3x1D LUT color transform for a given RGB triplet. + */ +void color_transform_lut_3x1d_eval(struct wlr_color_transform_lut_3x1d *tr, + float out[static 3], const float in[static 3]); + /** * Obtain primaries values from a well-known primaries name. */ diff --git a/include/wlr/render/color.h b/include/wlr/render/color.h index dc6f4097f..c2cbee265 100644 --- a/include/wlr/render/color.h +++ b/include/wlr/render/color.h @@ -10,6 +10,7 @@ #define WLR_RENDER_COLOR_H #include +#include #include /** @@ -78,6 +79,13 @@ struct wlr_color_transform *wlr_color_transform_init_linear_to_icc( */ struct wlr_color_transform *wlr_color_transform_init_srgb(void); +/** + * Initialize a color transformation to apply three 1D look-up tables. dim + * is the number of elements in each individual LUT. Returns NULL on failure. + */ +struct wlr_color_transform *wlr_color_transform_init_lut_3x1d(size_t dim, + const uint16_t *r, const uint16_t *g, const uint16_t *b); + /** * Increase the reference count of the color transform by 1. */ diff --git a/render/color.c b/render/color.c index 1c8365013..2bd8d65ec 100644 --- a/render/color.c +++ b/render/color.c @@ -38,6 +38,28 @@ struct wlr_color_transform *wlr_color_transform_init_srgb(void) { return tx; } +struct wlr_color_transform *wlr_color_transform_init_lut_3x1d(size_t dim, + const uint16_t *r, const uint16_t *g, const uint16_t *b) { + uint16_t *lut_3x1d = malloc(3 * dim * sizeof(lut_3x1d[0])); + if (lut_3x1d == NULL) { + return NULL; + } + + memcpy(&lut_3x1d[0 * dim], r, dim * sizeof(lut_3x1d[0])); + memcpy(&lut_3x1d[1 * dim], g, dim * sizeof(lut_3x1d[0])); + memcpy(&lut_3x1d[2 * dim], b, dim * sizeof(lut_3x1d[0])); + + struct wlr_color_transform_lut_3x1d *tx = calloc(1, sizeof(*tx)); + if (!tx) { + free(lut_3x1d); + return NULL; + } + wlr_color_transform_init(&tx->base, COLOR_TRANSFORM_LUT_3X1D); + tx->lut_3x1d = lut_3x1d; + tx->dim = dim; + return &tx->base; +} + static void color_transform_destroy(struct wlr_color_transform *tr) { switch (tr->type) { case COLOR_TRANSFORM_SRGB: @@ -45,6 +67,10 @@ static void color_transform_destroy(struct wlr_color_transform *tr) { case COLOR_TRANSFORM_LCMS2: color_transform_lcms2_finish(color_transform_lcms2_from_base(tr)); break; + case COLOR_TRANSFORM_LUT_3X1D:; + struct wlr_color_transform_lut_3x1d *lut_3x1d = color_transform_lut_3x1d_from_base(tr); + free(lut_3x1d->lut_3x1d); + break; } wlr_addon_set_finish(&tr->addons); free(tr); @@ -66,6 +92,37 @@ void wlr_color_transform_unref(struct wlr_color_transform *tr) { } } +struct wlr_color_transform_lut_3x1d *color_transform_lut_3x1d_from_base( + struct wlr_color_transform *tr) { + assert(tr->type == COLOR_TRANSFORM_LUT_3X1D); + struct wlr_color_transform_lut_3x1d *lut_3x1d = wl_container_of(tr, lut_3x1d, base); + return lut_3x1d; +} + +static float lut_1d_get(const uint16_t *lut, size_t len, size_t i) { + if (i > len) { + i = len - 1; + } + return (float) lut[i] / UINT16_MAX; +} + +static float lut_1d_eval(const uint16_t *lut, size_t len, float x) { + double pos = x * (len - 1); + double int_part; + double frac_part = modf(pos, &int_part); + size_t i = (size_t) int_part; + double a = lut_1d_get(lut, len, i); + double b = lut_1d_get(lut, len, i + 1); + return a * (1 - frac_part) + b * frac_part; +} + +void color_transform_lut_3x1d_eval(struct wlr_color_transform_lut_3x1d *tr, + float out[static 3], const float in[static 3]) { + for (size_t i = 0; i < 3; i++) { + out[i] = lut_1d_eval(&tr->lut_3x1d[tr->dim * i], tr->dim, in[i]); + } +} + void wlr_color_primaries_from_named(struct wlr_color_primaries *out, enum wlr_color_named_primaries named) { switch (named) { diff --git a/render/vulkan/pass.c b/render/vulkan/pass.c index 2ad6d9086..529743305 100644 --- a/render/vulkan/pass.c +++ b/render/vulkan/pass.c @@ -838,7 +838,7 @@ void vk_color_transform_destroy(struct wlr_addon *addon) { } static bool create_3d_lut_image(struct wlr_vk_renderer *renderer, - struct wlr_color_transform_lcms2 *tr, size_t dim_len, + struct wlr_color_transform *tr, size_t dim_len, VkImage *image, VkImageView *image_view, VkDeviceMemory *memory, VkDescriptorSet *ds, struct wlr_vk_descriptor_pool **ds_pool) { @@ -851,6 +851,19 @@ static bool create_3d_lut_image(struct wlr_vk_renderer *renderer, *ds = VK_NULL_HANDLE; *ds_pool = NULL; + struct wlr_color_transform_lcms2 *tr_lcms2 = NULL; + struct wlr_color_transform_lut_3x1d *tr_lut_3x1d = NULL; + switch (tr->type) { + case COLOR_TRANSFORM_SRGB: + abort(); // unreachable + case COLOR_TRANSFORM_LCMS2: + tr_lcms2 = color_transform_lcms2_from_base(tr); + break; + case COLOR_TRANSFORM_LUT_3X1D: + tr_lut_3x1d = color_transform_lut_3x1d_from_base(tr); + break; + } + // R32G32B32 is not a required Vulkan format // TODO: use it when available VkFormat format = VK_FORMAT_R32G32B32A32_SFLOAT; @@ -948,7 +961,11 @@ static bool create_3d_lut_image(struct wlr_vk_renderer *renderer, b_index * sample_range, }; float rgb_out[3]; - color_transform_lcms2_eval(tr, rgb_out, rgb_in); + if (tr_lcms2 != NULL) { + color_transform_lcms2_eval(tr_lcms2, rgb_out, rgb_in); + } else { + color_transform_lut_3x1d_eval(tr_lut_3x1d, rgb_out, rgb_in); + } dst[dst_offset] = rgb_out[0]; dst[dst_offset + 1] = rgb_out[1]; @@ -1018,10 +1035,9 @@ static struct wlr_vk_color_transform *vk_color_transform_create( return NULL; } - if (transform->type == COLOR_TRANSFORM_LCMS2) { + if (transform->type != COLOR_TRANSFORM_SRGB) { vk_transform->lut_3d.dim = 33; - if (!create_3d_lut_image(renderer, - color_transform_lcms2_from_base(transform), + if (!create_3d_lut_image(renderer, transform, vk_transform->lut_3d.dim, &vk_transform->lut_3d.image, &vk_transform->lut_3d.image_view,