From 42cf99a0378c93a270b601f5503c1d2c912597ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Poisot?= Date: Sat, 31 Jan 2026 21:04:51 +0000 Subject: [PATCH 1/5] render/color: introduce wlr_color_transform_eotf --- include/render/color.h | 14 +++++++ include/wlr/render/color.h | 7 ++++ render/color.c | 77 ++++++++++++++++++++++++++++++++++++++ render/vulkan/pass.c | 1 + 4 files changed, 99 insertions(+) diff --git a/include/render/color.h b/include/render/color.h index 60c841d12..232b48b7b 100644 --- a/include/render/color.h +++ b/include/render/color.h @@ -11,6 +11,7 @@ enum wlr_color_transform_type { COLOR_TRANSFORM_LUT_3X1D, COLOR_TRANSFORM_MATRIX, COLOR_TRANSFORM_PIPELINE, + COLOR_TRANSFORM_EOTF, }; struct wlr_color_transform { @@ -20,6 +21,12 @@ struct wlr_color_transform { enum wlr_color_transform_type type; }; +struct wlr_color_transform_eotf { + struct wlr_color_transform base; + + enum wlr_color_transfer_function tf; +}; + struct wlr_color_transform_inverse_eotf { struct wlr_color_transform base; @@ -72,6 +79,13 @@ 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]); +/** + * Gets a wlr_color_transform_eotf from a generic wlr_color_transform. + * Asserts that the base type is COLOR_TRANSFORM_EOTF + */ +struct wlr_color_transform_eotf *wlr_color_transform_eotf_from_base( + struct wlr_color_transform *tr); + /** * Gets a wlr_color_transform_inverse_eotf from a generic wlr_color_transform. * Asserts that the base type is COLOR_TRANSFORM_INVERSE_EOTF diff --git a/include/wlr/render/color.h b/include/wlr/render/color.h index 31d2c85e9..d939b06d2 100644 --- a/include/wlr/render/color.h +++ b/include/wlr/render/color.h @@ -127,6 +127,13 @@ struct wlr_color_transform; struct wlr_color_transform *wlr_color_transform_init_linear_to_icc( const void *data, size_t size); +/** + * Initialize a color transformation to apply EOTF decoding. Returns + * NULL on failure. + */ +struct wlr_color_transform *wlr_color_transform_init_eotf_to_linear( + enum wlr_color_transfer_function tf); + /** * Initialize a color transformation to apply EOTF⁻¹ encoding. Returns * NULL on failure. diff --git a/render/color.c b/render/color.c index e89fab260..a25e249cd 100644 --- a/render/color.c +++ b/render/color.c @@ -29,6 +29,17 @@ void wlr_color_transform_init(struct wlr_color_transform *tr, enum wlr_color_tra wlr_addon_set_init(&tr->addons); } +struct wlr_color_transform *wlr_color_transform_init_eotf_to_linear( + enum wlr_color_transfer_function tf) { + struct wlr_color_transform_inverse_eotf *tx = calloc(1, sizeof(*tx)); + if (!tx) { + return NULL; + } + wlr_color_transform_init(&tx->base, COLOR_TRANSFORM_EOTF); + tx->tf = tf; + return &tx->base; +} + struct wlr_color_transform *wlr_color_transform_init_linear_to_inverse_eotf( enum wlr_color_transfer_function tf) { struct wlr_color_transform_inverse_eotf *tx = calloc(1, sizeof(*tx)); @@ -103,6 +114,7 @@ static void color_transform_destroy(struct wlr_color_transform *tr) { switch (tr->type) { case COLOR_TRANSFORM_INVERSE_EOTF: case COLOR_TRANSFORM_MATRIX: + case COLOR_TRANSFORM_EOTF: break; case COLOR_TRANSFORM_LCMS2: color_transform_lcms2_finish(color_transform_lcms2_from_base(tr)); @@ -140,6 +152,13 @@ void wlr_color_transform_unref(struct wlr_color_transform *tr) { } } +struct wlr_color_transform_eotf *wlr_color_transform_eotf_from_base( + struct wlr_color_transform *tr) { + assert(tr->type == COLOR_TRANSFORM_EOTF); + struct wlr_color_transform_eotf *eotf = wl_container_of(tr, eotf, base); + return eotf; +} + struct wlr_color_transform_inverse_eotf *wlr_color_transform_inverse_eotf_from_base( struct wlr_color_transform *tr) { assert(tr->type == COLOR_TRANSFORM_INVERSE_EOTF); @@ -154,6 +173,14 @@ struct wlr_color_transform_lut_3x1d *color_transform_lut_3x1d_from_base( return lut_3x1d; } +static float srgb_eval_eotf(float x) { + if (x <= 0.04045) { + return x / 12.92; + } else { + return powf((x + 0.055) / 1.055, 2.4); + } +} + static float srgb_eval_inverse_eotf(float x) { // See https://www.w3.org/Graphics/Color/srgb if (x <= 0.0031308) { @@ -163,6 +190,21 @@ static float srgb_eval_inverse_eotf(float x) { } } +static float st2084_pq_eval_eotf(float x) { + float c1 = 0.8359375; + float c2 = 18.8515625; + float c3 = 18.6875; + float inv_m2 = 1.0 / 78.84375; + float inv_m1 = 1.0 / 0.1593017578125; + float pow_m2 = powf(x, inv_m2); + float num = pow_m2 - c1; + if (num < 0) { + num = 0; + } + float denom = c2 - c3 * pow_m2; + return powf(num / denom, inv_m1); +} + static float st2084_pq_eval_inverse_eotf(float x) { // H.273 TransferCharacteristics code point 16 float c1 = 0.8359375; @@ -180,6 +222,14 @@ static float st2084_pq_eval_inverse_eotf(float x) { return powf((c1 + c2 * pow_n) / (1 + c3 * pow_n), m); } +static float bt1886_eval_eotf(float x) { + float lb = powf(0.0001, 1.0 / 2.4); + float lw = powf(1.0, 1.0 / 2.4); + float a = powf(lw - lb, 2.4); + float b = lb / (lw - lb); + return a * powf(x + b, 2.4); +} + static float bt1886_eval_inverse_eotf(float x) { float lb = powf(0.0001, 1.0 / 2.4); float lw = powf(1.0, 1.0 / 2.4); @@ -188,6 +238,22 @@ static float bt1886_eval_inverse_eotf(float x) { return powf(x / a, 1.0 / 2.4) - b; } +static float transfer_function_eval_eotf(enum wlr_color_transfer_function tf, float x) { + switch (tf) { + case WLR_COLOR_TRANSFER_FUNCTION_SRGB: + return srgb_eval_eotf(x); + case WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ: + return st2084_pq_eval_eotf(x); + case WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR: + return x; + case WLR_COLOR_TRANSFER_FUNCTION_GAMMA22: + return powf(x, 2.2); + case WLR_COLOR_TRANSFER_FUNCTION_BT1886: + return bt1886_eval_eotf(x); + } + abort(); // unreachable +} + static float transfer_function_eval_inverse_eotf( enum wlr_color_transfer_function tf, float x) { switch (tf) { @@ -205,6 +271,14 @@ static float transfer_function_eval_inverse_eotf( abort(); // unreachable } +static void color_transform_eotf_eval( + struct wlr_color_transform_eotf *tr, + float out[static 3], const float in[static 3]) { + for (size_t i = 0; i < 3; i++) { + out[i] = transfer_function_eval_eotf(tr->tf, in[i]); + } +} + static void color_transform_inverse_eotf_eval( struct wlr_color_transform_inverse_eotf *tr, float out[static 3], const float in[static 3]) { @@ -265,6 +339,9 @@ void wlr_color_transform_eval(struct wlr_color_transform *tr, } memcpy(out, color, sizeof(color)); break; + case COLOR_TRANSFORM_EOTF: + color_transform_eotf_eval(wlr_color_transform_eotf_from_base(tr), out, in); + break; } } diff --git a/render/vulkan/pass.c b/render/vulkan/pass.c index 01e8fbd7a..6f1702583 100644 --- a/render/vulkan/pass.c +++ b/render/vulkan/pass.c @@ -169,6 +169,7 @@ static bool unwrap_color_transform(struct wlr_color_transform *transform, return true; case COLOR_TRANSFORM_LCMS2: case COLOR_TRANSFORM_LUT_3X1D: + case COLOR_TRANSFORM_EOTF: return false; } return false; From 03d3041fba1df911bb84275db5e02b01cbd75d45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Poisot?= Date: Sat, 31 Jan 2026 21:30:55 +0000 Subject: [PATCH 2/5] render: make wlr_render_pass_add_texture() take a general color_transform Mirrors the previous changes to wlr_renderer_begin_buffer_pass(), moving color-management decisions to wlr_scene --- include/render/vulkan.h | 1 - include/wlr/render/pass.h | 8 +-- include/wlr/types/wlr_output.h | 3 +- render/vulkan/pass.c | 69 +++++++++++++++++-------- render/vulkan/shaders/texture.frag | 3 -- types/output/cursor.c | 37 ++++++++++---- types/scene/wlr_scene.c | 81 +++++++++++++++++++++++++----- 7 files changed, 148 insertions(+), 54 deletions(-) diff --git a/include/render/vulkan.h b/include/render/vulkan.h index 021749c27..889d60d8a 100644 --- a/include/render/vulkan.h +++ b/include/render/vulkan.h @@ -374,7 +374,6 @@ struct wlr_vk_vert_pcr_data { struct wlr_vk_frag_texture_pcr_data { float matrix[4][4]; // only a 3x3 subset is used float alpha; - float luminance_multiplier; }; struct wlr_vk_frag_output_pcr_data { diff --git a/include/wlr/render/pass.h b/include/wlr/render/pass.h index 61ab77132..407e65366 100644 --- a/include/wlr/render/pass.h +++ b/include/wlr/render/pass.h @@ -101,16 +101,12 @@ struct wlr_render_texture_options { enum wlr_scale_filter_mode filter_mode; /* Blend mode */ enum wlr_render_blend_mode blend_mode; - /* Transfer function the source texture is encoded with */ - enum wlr_color_transfer_function transfer_function; - /* Primaries describing the color volume of the source texture */ - const struct wlr_color_primaries *primaries; + /* Applied to convert from source texture to blend space */ + struct wlr_color_transform *color_transform; /* Color encoding of the source texture for YCbCr conversion to RGB */ enum wlr_color_encoding color_encoding; /* Color range of the source texture */ enum wlr_color_range color_range; - /* Default: 1.0 */ - const float *luminance_multiplier; /* Wait for a timeline synchronization point before texturing. * diff --git a/include/wlr/types/wlr_output.h b/include/wlr/types/wlr_output.h index c8e44b0e6..654cedaff 100644 --- a/include/wlr/types/wlr_output.h +++ b/include/wlr/types/wlr_output.h @@ -53,7 +53,8 @@ struct wlr_output_cursor { struct { struct wl_listener renderer_destroy; - struct wlr_color_transform *color_transform; + struct wlr_color_transform *color_transform_linearize; + struct wlr_color_transform *color_transform_encode; } WLR_PRIVATE; }; diff --git a/render/vulkan/pass.c b/render/vulkan/pass.c index 6f1702583..d2b740ee5 100644 --- a/render/vulkan/pass.c +++ b/render/vulkan/pass.c @@ -134,7 +134,7 @@ static bool render_pass_wait_render_buffer(struct wlr_vk_render_pass *pass, return true; } -static bool unwrap_color_transform(struct wlr_color_transform *transform, +static bool unwrap_output_color_transform(struct wlr_color_transform *transform, float matrix[static 9], enum wlr_color_transfer_function *tf) { if (transform == NULL) { wlr_matrix_identity(matrix); @@ -175,6 +175,47 @@ static bool unwrap_color_transform(struct wlr_color_transform *transform, return false; } +static bool unwrap_texture_color_transform(struct wlr_color_transform *transform, + float matrix[static 9], enum wlr_color_transfer_function *tf) { + if (transform == NULL) { + wlr_matrix_identity(matrix); + *tf = WLR_COLOR_TRANSFER_FUNCTION_GAMMA22; + return true; + } + struct wlr_color_transform_eotf *eotf; + struct wlr_color_transform_matrix *as_matrix; + struct wlr_color_transform_pipeline *pipeline; + switch (transform->type) { + case COLOR_TRANSFORM_EOTF: + eotf = wlr_color_transform_eotf_from_base(transform); + wlr_matrix_identity(matrix); + *tf = eotf->tf; + return true; + case COLOR_TRANSFORM_MATRIX: + as_matrix = wl_container_of(transform, as_matrix, base); + memcpy(matrix, as_matrix->matrix, sizeof(float[9])); + *tf = WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR; + return true; + case COLOR_TRANSFORM_PIPELINE: + pipeline = wl_container_of(transform, pipeline, base); + if (pipeline->len != 2 + || pipeline->transforms[0]->type != COLOR_TRANSFORM_EOTF + || pipeline->transforms[1]->type != COLOR_TRANSFORM_MATRIX) { + return false; + } + eotf = wlr_color_transform_eotf_from_base(pipeline->transforms[0]); + *tf = eotf->tf; + as_matrix = wl_container_of(pipeline->transforms[1], as_matrix, base); + memcpy(matrix, as_matrix->matrix, sizeof(float[9])); + return true; + case COLOR_TRANSFORM_INVERSE_EOTF: + case COLOR_TRANSFORM_LCMS2: + case COLOR_TRANSFORM_LUT_3X1D: + return false; + } + return false; +} + static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { struct wlr_vk_render_pass *pass = get_render_pass(wlr_pass); struct wlr_vk_renderer *renderer = pass->renderer; @@ -789,9 +830,12 @@ static void render_pass_add_texture(struct wlr_render_pass *wlr_pass, }; encode_proj_matrix(matrix, vert_pcr_data.mat4); - enum wlr_color_transfer_function tf = options->transfer_function; - if (tf == 0) { + enum wlr_color_transfer_function tf; + float color_matrix[9]; + if (!unwrap_texture_color_transform(options->color_transform, color_matrix, &tf)) + { tf = WLR_COLOR_TRANSFER_FUNCTION_GAMMA22; + wlr_matrix_identity(color_matrix); } bool srgb_image_view = false; @@ -858,25 +902,8 @@ static void render_pass_add_texture(struct wlr_render_pass *wlr_pass, return; } - float color_matrix[9]; - if (options->primaries != NULL) { - struct wlr_color_primaries srgb; - wlr_color_primaries_from_named(&srgb, WLR_COLOR_NAMED_PRIMARIES_SRGB); - - wlr_color_primaries_transform_absolute_colorimetric(options->primaries, - &srgb, color_matrix); - } else { - wlr_matrix_identity(color_matrix); - } - - float luminance_multiplier = 1; - if (options->luminance_multiplier != NULL) { - luminance_multiplier = *options->luminance_multiplier; - } - struct wlr_vk_frag_texture_pcr_data frag_pcr_data = { .alpha = alpha, - .luminance_multiplier = luminance_multiplier, }; encode_color_matrix(color_matrix, frag_pcr_data.matrix); @@ -1147,7 +1174,7 @@ static struct wlr_vk_color_transform *vk_color_transform_create( return NULL; } - bool need_lut = !unwrap_color_transform(transform, vk_transform->color_matrix, + bool need_lut = !unwrap_output_color_transform(transform, vk_transform->color_matrix, &vk_transform->inverse_eotf); if (need_lut) { diff --git a/render/vulkan/shaders/texture.frag b/render/vulkan/shaders/texture.frag index 3ec974235..03c24eceb 100644 --- a/render/vulkan/shaders/texture.frag +++ b/render/vulkan/shaders/texture.frag @@ -9,7 +9,6 @@ layout(location = 0) out vec4 out_color; layout(push_constant, row_major) uniform UBO { layout(offset = 80) mat4 matrix; float alpha; - float luminance_multiplier; } data; layout (constant_id = 0) const int TEXTURE_TRANSFORM = 0; @@ -82,8 +81,6 @@ void main() { rgb = bt1886_color_to_linear(rgb); } - rgb *= data.luminance_multiplier; - rgb = mat3(data.matrix) * rgb; // Back to pre-multiplied alpha diff --git a/types/output/cursor.c b/types/output/cursor.c index 4a823dab1..58f58752f 100644 --- a/types/output/cursor.c +++ b/types/output/cursor.c @@ -256,7 +256,7 @@ static struct wlr_buffer *render_cursor_buffer(struct wlr_output_cursor *cursor) buffer->width, buffer->height); struct wlr_buffer_pass_options options = { - .color_transform = cursor->color_transform, + .color_transform = cursor->color_transform_encode, }; struct wlr_render_pass *pass = wlr_renderer_begin_buffer_pass(renderer, buffer, &options); if (pass == NULL) { @@ -278,6 +278,7 @@ static struct wlr_buffer *render_cursor_buffer(struct wlr_output_cursor *cursor) .transform = transform, .wait_timeline = cursor->wait_timeline, .wait_point = cursor->wait_point, + .color_transform = cursor->color_transform_linearize, }); if (!wlr_render_pass_submit(pass)) { @@ -493,18 +494,27 @@ void wlr_output_cursor_destroy(struct wlr_output_cursor *cursor) { } wlr_drm_syncobj_timeline_unref(cursor->wait_timeline); wl_list_remove(&cursor->link); - wlr_color_transform_unref(cursor->color_transform); + wlr_color_transform_unref(cursor->color_transform_linearize); + wlr_color_transform_unref(cursor->color_transform_encode); free(cursor); } bool output_cursor_refresh_color_transform(struct wlr_output_cursor *output_cursor, const struct wlr_output_image_description *img_desc) { - wlr_color_transform_unref(output_cursor->color_transform); - output_cursor->color_transform = NULL; + wlr_color_transform_unref(output_cursor->color_transform_linearize); + output_cursor->color_transform_linearize = NULL; + wlr_color_transform_unref(output_cursor->color_transform_encode); + output_cursor->color_transform_encode = NULL; if (img_desc == NULL) { return true; } + output_cursor->color_transform_linearize = wlr_color_transform_init_eotf_to_linear( + WLR_COLOR_TRANSFER_FUNCTION_GAMMA22); + if (output_cursor->color_transform_linearize == NULL) { + goto err; + } + struct wlr_color_primaries primaries_srgb; wlr_color_primaries_from_named(&primaries_srgb, WLR_COLOR_NAMED_PRIMARIES_SRGB); struct wlr_color_primaries primaries; @@ -525,13 +535,22 @@ bool output_cursor_refresh_color_transform(struct wlr_output_cursor *output_curs wlr_color_transform_init_linear_to_inverse_eotf(img_desc->transfer_function), }; if (transforms[0] == NULL || transforms[1] == NULL) { - wlr_color_transform_unref(transforms[0]); - wlr_color_transform_unref(transforms[1]); - return false; + goto err; } - output_cursor->color_transform = wlr_color_transform_init_pipeline(transforms, + output_cursor->color_transform_encode = wlr_color_transform_init_pipeline(transforms, sizeof(transforms) / sizeof(transforms[0])); + if (output_cursor->color_transform_encode == NULL) { + goto err; + } + wlr_color_transform_unref(transforms[0]); wlr_color_transform_unref(transforms[1]); - return output_cursor->color_transform != NULL; + return true; + +err: + wlr_color_transform_unref(output_cursor->color_transform_linearize); + output_cursor->color_transform_linearize = NULL; + wlr_color_transform_unref(transforms[0]); + wlr_color_transform_unref(transforms[1]); + return false; } diff --git a/types/scene/wlr_scene.c b/types/scene/wlr_scene.c index 7231422e8..c086f8676 100644 --- a/types/scene/wlr_scene.c +++ b/types/scene/wlr_scene.c @@ -20,6 +20,7 @@ #include "types/wlr_scene.h" #include "util/array.h" #include "util/env.h" +#include "util/matrix.h" #include "util/time.h" #include @@ -1411,6 +1412,63 @@ static float get_luminance_multiplier(const struct wlr_color_luminances *src_lum return (dst_lum->reference / src_lum->reference) * (src_lum->max / dst_lum->max); } +static struct wlr_color_transform *scene_texture_to_blend_space( + struct wlr_scene_buffer *source, enum wlr_color_named_primaries dest_primaries, + const struct wlr_color_luminances *dest_luminance) { + struct wlr_color_transform *color_matrix = NULL; + struct wlr_color_transform *eotf = NULL; + struct wlr_color_transform *combined = NULL; + + enum wlr_color_transfer_function source_tf = WLR_COLOR_TRANSFER_FUNCTION_GAMMA22; + if (source->transfer_function != 0) { + source_tf = source->transfer_function; + } + eotf = wlr_color_transform_init_eotf_to_linear(source_tf); + if (eotf == NULL) { + goto cleanup_transforms; + } + + struct wlr_color_luminances source_lum; + wlr_color_transfer_function_get_default_luminance(source_tf, &source_lum); + float luminance_multiplier = get_luminance_multiplier(&source_lum, dest_luminance); + + float matrix[9]; + + enum wlr_color_named_primaries source_primaries = WLR_COLOR_NAMED_PRIMARIES_SRGB; + if (source->primaries != 0) { + source_primaries = source->primaries; + } + if (source_primaries != dest_primaries) { + struct wlr_color_primaries primaries; + wlr_color_primaries_from_named(&primaries, source_primaries); + struct wlr_color_primaries primaries_blend; + wlr_color_primaries_from_named(&primaries_blend, dest_primaries); + wlr_color_primaries_transform_absolute_colorimetric(&primaries, &primaries_blend, matrix); + } else { + wlr_matrix_identity(matrix); + } + + for (int i = 0; i < 9; ++i) { + matrix[i] *= luminance_multiplier; + } + color_matrix = wlr_color_transform_init_matrix(matrix); + if (color_matrix == NULL) { + goto cleanup_transforms; + } + + struct wlr_color_transform *transforms[] = { + eotf, + color_matrix, + }; + const size_t transforms_len = sizeof(transforms) / sizeof(transforms[0]); + color_transform_compose(&combined, transforms, transforms_len); + +cleanup_transforms: + wlr_color_transform_unref(eotf); + wlr_color_transform_unref(color_matrix); + return combined; +} + static void scene_entry_render(struct render_list_entry *entry, const struct render_data *data) { struct wlr_scene_node *node = entry->node; @@ -1489,17 +1547,14 @@ static void scene_entry_render(struct render_list_entry *entry, const struct ren wlr_output_transform_invert(scene_buffer->transform); transform = wlr_output_transform_compose(transform, data->transform); - struct wlr_color_primaries primaries = {0}; - if (scene_buffer->primaries != 0) { - wlr_color_primaries_from_named(&primaries, scene_buffer->primaries); - } + struct wlr_color_transform *source_to_blend = NULL; + if (data->output->output->renderer->features.input_color_transform) { + struct wlr_color_luminances srgb_lum; + wlr_color_transfer_function_get_default_luminance(WLR_COLOR_TRANSFER_FUNCTION_SRGB, &srgb_lum); - struct wlr_color_luminances src_lum, srgb_lum; - wlr_color_transfer_function_get_default_luminance( - scene_buffer->transfer_function, &src_lum); - wlr_color_transfer_function_get_default_luminance( - WLR_COLOR_TRANSFER_FUNCTION_SRGB, &srgb_lum); - float luminance_multiplier = get_luminance_multiplier(&src_lum, &srgb_lum); + source_to_blend = scene_texture_to_blend_space( + scene_buffer, WLR_COLOR_NAMED_PRIMARIES_SRGB, &srgb_lum); + } wlr_render_pass_add_texture(data->render_pass, &(struct wlr_render_texture_options) { .texture = texture, @@ -1512,15 +1567,15 @@ static void scene_entry_render(struct render_list_entry *entry, const struct ren .blend_mode = !data->output->scene->calculate_visibility || !pixman_region32_empty(&opaque) ? WLR_RENDER_BLEND_MODE_PREMULTIPLIED : WLR_RENDER_BLEND_MODE_NONE, - .transfer_function = scene_buffer->transfer_function, - .primaries = scene_buffer->primaries != 0 ? &primaries : NULL, + .color_transform = source_to_blend, .color_encoding = scene_buffer->color_encoding, .color_range = scene_buffer->color_range, - .luminance_multiplier = &luminance_multiplier, .wait_timeline = scene_buffer->wait_timeline, .wait_point = scene_buffer->wait_point, }); + wlr_color_transform_unref(source_to_blend); + struct wlr_scene_output_sample_event sample_event = { .output = data->output, .direct_scanout = false, From bf8b6a71273f60ef9fe003e0e12ac2d40ac5a860 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Poisot?= Date: Sun, 1 Feb 2026 15:14:36 +0000 Subject: [PATCH 3/5] render/vulkan: don't assume a default EOTF All color-management decisions, including the use of a linear space for blending, are now moved up to wlr_scene --- include/wlr/render/pass.h | 4 +- render/vulkan/pass.c | 31 +++++---------- types/scene/wlr_scene.c | 83 +++++++++++++++++++++++++++------------ 3 files changed, 68 insertions(+), 50 deletions(-) diff --git a/include/wlr/render/pass.h b/include/wlr/render/pass.h index 407e65366..876634d8f 100644 --- a/include/wlr/render/pass.h +++ b/include/wlr/render/pass.h @@ -31,9 +31,7 @@ struct wlr_render_timer; struct wlr_buffer_pass_options { /* Timer to measure the duration of the render pass */ struct wlr_render_timer *timer; - /* Color transform to apply to the output of the render pass. - * Leave NULL to indicate the default transform (Gamma 2.2 encoding for - * sRGB monitors) */ + /* Color transform to apply to the output of the render pass */ struct wlr_color_transform *color_transform; /* Signal a timeline synchronization point when the render pass completes. diff --git a/render/vulkan/pass.c b/render/vulkan/pass.c index d2b740ee5..e377c4214 100644 --- a/render/vulkan/pass.c +++ b/render/vulkan/pass.c @@ -56,14 +56,6 @@ static void convert_pixman_box_to_vk_rect(const pixman_box32_t *box, VkRect2D *r }; } -static float color_to_linear(float non_linear) { - return pow(non_linear, 2.2); -} - -static float color_to_linear_premult(float non_linear, float alpha) { - return (alpha == 0) ? 0 : color_to_linear(non_linear / alpha) * alpha; -} - static void encode_proj_matrix(const float mat3[9], float mat4[4][4]) { float result[4][4] = { { mat3[0], mat3[1], 0, mat3[2] }, @@ -138,7 +130,7 @@ static bool unwrap_output_color_transform(struct wlr_color_transform *transform, float matrix[static 9], enum wlr_color_transfer_function *tf) { if (transform == NULL) { wlr_matrix_identity(matrix); - *tf = WLR_COLOR_TRANSFER_FUNCTION_GAMMA22; + *tf = WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR; return true; } struct wlr_color_transform_inverse_eotf *eotf; @@ -179,7 +171,7 @@ static bool unwrap_texture_color_transform(struct wlr_color_transform *transform float matrix[static 9], enum wlr_color_transfer_function *tf) { if (transform == NULL) { wlr_matrix_identity(matrix); - *tf = WLR_COLOR_TRANSFER_FUNCTION_GAMMA22; + *tf = WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR; return true; } struct wlr_color_transform_eotf *eotf; @@ -256,7 +248,7 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { encode_proj_matrix(final_matrix, vert_pcr_data.mat4); float matrix[9]; - enum wlr_color_transfer_function tf = WLR_COLOR_TRANSFER_FUNCTION_GAMMA22; + enum wlr_color_transfer_function tf = WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR; bool need_lut = false; size_t dim = 1; struct wlr_vk_color_transform *transform = NULL; @@ -688,16 +680,11 @@ static void render_pass_add_rect(struct wlr_render_pass *wlr_pass, struct wlr_vk_render_pass *pass = get_render_pass(wlr_pass); VkCommandBuffer cb = pass->command_buffer->vk; - // Input color values are given in sRGB space, shader expects - // them in linear space. The shader does all computation in linear - // space and expects in inputs in linear space since it outputs - // colors in linear space as well (and vulkan then automatically - // does the conversion for out sRGB render targets). float linear_color[] = { - color_to_linear_premult(options->color.r, options->color.a), - color_to_linear_premult(options->color.g, options->color.a), - color_to_linear_premult(options->color.b, options->color.a), - options->color.a, // no conversion for alpha + options->color.r, + options->color.g, + options->color.b, + options->color.a, }; pixman_region32_t clip; @@ -834,7 +821,7 @@ static void render_pass_add_texture(struct wlr_render_pass *wlr_pass, float color_matrix[9]; if (!unwrap_texture_color_transform(options->color_transform, color_matrix, &tf)) { - tf = WLR_COLOR_TRANSFER_FUNCTION_GAMMA22; + tf = WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR; wlr_matrix_identity(color_matrix); } @@ -1218,7 +1205,7 @@ struct wlr_vk_render_pass *vulkan_begin_render_pass(struct wlr_vk_renderer *rend } } else { // This is the default when unspecified - inv_eotf = WLR_COLOR_TRANSFER_FUNCTION_GAMMA22; + inv_eotf = WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR; } bool using_linear_pathway = inv_eotf == WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR; diff --git a/types/scene/wlr_scene.c b/types/scene/wlr_scene.c index c086f8676..2a3b1f6bd 100644 --- a/types/scene/wlr_scene.c +++ b/types/scene/wlr_scene.c @@ -320,6 +320,8 @@ struct render_data { struct wlr_render_pass *render_pass; pixman_region32_t damage; + + struct wlr_color_transform *default_color_transform; }; static void logical_to_buffer_coords(pixman_region32_t *region, const struct render_data *data, @@ -1469,6 +1471,28 @@ cleanup_transforms: return combined; } +static struct wlr_render_color scene_color_transform_premultiplied( + struct wlr_color_transform *transform, float r, float g, float b, float a) { + float transform_in[] = { + (a == 0) ? 0 : r / a, + (a == 0) ? 0 : g / a, + (a == 0) ? 0 : b / a, + }; + float transform_out[] = {0, 0, 0}; + if (transform != NULL) { + wlr_color_transform_eval(transform, transform_out, transform_in); + } else { + memcpy(transform_out, transform_in, sizeof(transform_in)); + } + struct wlr_render_color res = { + .r = transform_out[0] * a, + .g = transform_out[1] * a, + .b = transform_out[2] * a, + .a = a, + }; + return res; +} + static void scene_entry_render(struct render_list_entry *entry, const struct render_data *data) { struct wlr_scene_node *node = entry->node; @@ -1508,12 +1532,11 @@ static void scene_entry_render(struct render_list_entry *entry, const struct ren wlr_render_pass_add_rect(data->render_pass, &(struct wlr_render_rect_options){ .box = dst_box, - .color = { - .r = scene_rect->color[0], - .g = scene_rect->color[1], - .b = scene_rect->color[2], - .a = scene_rect->color[3], - }, + .color = scene_color_transform_premultiplied(data->default_color_transform, + scene_rect->color[0], + scene_rect->color[1], + scene_rect->color[2], + scene_rect->color[3]), .clip = &render_region, }); break; @@ -1524,13 +1547,12 @@ static void scene_entry_render(struct render_list_entry *entry, const struct ren // Render the buffer as a rect, this is likely to be more efficient wlr_render_pass_add_rect(data->render_pass, &(struct wlr_render_rect_options){ .box = dst_box, - .color = { - .r = (float)scene_buffer->single_pixel_buffer_color[0] / (float)UINT32_MAX, - .g = (float)scene_buffer->single_pixel_buffer_color[1] / (float)UINT32_MAX, - .b = (float)scene_buffer->single_pixel_buffer_color[2] / (float)UINT32_MAX, - .a = (float)scene_buffer->single_pixel_buffer_color[3] / - (float)UINT32_MAX * scene_buffer->opacity, - }, + .color = scene_color_transform_premultiplied(data->default_color_transform, + (float)scene_buffer->single_pixel_buffer_color[0] / (float)UINT32_MAX, + (float)scene_buffer->single_pixel_buffer_color[1] / (float)UINT32_MAX, + (float)scene_buffer->single_pixel_buffer_color[2] / (float)UINT32_MAX, + (float)scene_buffer->single_pixel_buffer_color[3] / + (float)UINT32_MAX * scene_buffer->opacity), .clip = &render_region, }); break; @@ -1587,7 +1609,8 @@ static void scene_entry_render(struct render_list_entry *entry, const struct ren if (entry->highlight_transparent_region) { wlr_render_pass_add_rect(data->render_pass, &(struct wlr_render_rect_options){ .box = dst_box, - .color = { .r = 0, .g = 0.3, .b = 0, .a = 0.3 }, + .color = scene_color_transform_premultiplied(data->default_color_transform, + 0, 0.3, 0, 0.3), .clip = &opaque, }); } @@ -2523,17 +2546,23 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, timer->pre_render_duration = timespec_to_nsec(&duration); } - if ((render_gamma_lut - && scene_output->gamma_lut_color_transform != scene_output->prev_gamma_lut_color_transform) - || scene_output->prev_supplied_color_transform != options->color_transform - || (state->committed & WLR_OUTPUT_STATE_IMAGE_DESCRIPTION)) { - const struct wlr_output_image_description *output_description = - output_pending_image_description(output, state); - if (!scene_output_combine_color_transforms(scene_output, options->color_transform, - output_description, render_gamma_lut)) { - wlr_buffer_unlock(buffer); - return false; + if (output->renderer->features.output_color_transform) { + if ((render_gamma_lut + && scene_output->gamma_lut_color_transform != scene_output->prev_gamma_lut_color_transform) + || scene_output->prev_supplied_color_transform != options->color_transform + || (state->committed & WLR_OUTPUT_STATE_IMAGE_DESCRIPTION) + || scene_output->combined_color_transform == NULL) { + const struct wlr_output_image_description *output_description = + output_pending_image_description(output, state); + if (!scene_output_combine_color_transforms(scene_output, options->color_transform, + output_description, render_gamma_lut)) { + wlr_buffer_unlock(buffer); + return false; + } } + + render_data.default_color_transform = wlr_color_transform_init_eotf_to_linear( + WLR_COLOR_TRANSFER_FUNCTION_GAMMA22); } scene_output->in_point++; @@ -2546,6 +2575,7 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, }); if (render_pass == NULL) { wlr_buffer_unlock(buffer); + wlr_color_transform_unref(render_data.default_color_transform); return false; } @@ -2630,7 +2660,8 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, wlr_render_pass_add_rect(render_pass, &(struct wlr_render_rect_options){ .box = { .width = buffer->width, .height = buffer->height }, - .color = { .r = alpha * 0.5, .g = 0, .b = 0, .a = alpha * 0.5 }, + .color = scene_color_transform_premultiplied(render_data.default_color_transform, + alpha * 0.5, 0, 0, alpha * 0.5), .clip = &damage->region, }); } @@ -2641,6 +2672,7 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, if (!wlr_render_pass_submit(render_pass)) { wlr_buffer_unlock(buffer); + wlr_color_transform_unref(render_data.default_color_transform); // if we failed to render the buffer, it will have undefined contents // Trash the damage ring @@ -2650,6 +2682,7 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, wlr_output_state_set_buffer(state, buffer); wlr_buffer_unlock(buffer); + wlr_color_transform_unref(render_data.default_color_transform); if (scene_output->in_timeline != NULL) { wlr_output_state_set_wait_timeline(state, scene_output->in_timeline, From 611e0649c50f625488b9136923f0d0bbab8917ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Poisot?= Date: Thu, 5 Feb 2026 20:14:53 +0000 Subject: [PATCH 4/5] render/vulkan: 3D LUT fallback for texture pass color transform --- include/render/vulkan.h | 20 +++-- render/vulkan/pass.c | 117 +++++++++++++++++++++-------- render/vulkan/renderer.c | 47 ++++++++---- render/vulkan/shaders/texture.frag | 9 +++ 4 files changed, 140 insertions(+), 53 deletions(-) diff --git a/include/render/vulkan.h b/include/render/vulkan.h index 889d60d8a..b3624bb61 100644 --- a/include/render/vulkan.h +++ b/include/render/vulkan.h @@ -168,6 +168,7 @@ enum wlr_vk_texture_transform { WLR_VK_TEXTURE_TRANSFORM_ST2084_PQ = 2, WLR_VK_TEXTURE_TRANSFORM_GAMMA22 = 3, WLR_VK_TEXTURE_TRANSFORM_BT1886 = 4, + WLR_VK_TEXTURE_TRANSFORM_LUT_3D = 5, }; enum wlr_vk_shader_source { @@ -286,8 +287,8 @@ struct wlr_vk_command_buffer { struct wl_list destroy_textures; // wlr_vk_texture.destroy_link // Staging shared buffers to release after the command buffer completes struct wl_list stage_buffers; // wlr_vk_shared_buffer.link - // Color transform to unref after the command buffer completes - struct wlr_color_transform *color_transform; + // Color transforms to unref after the command buffer completes + struct wl_array color_transforms; // struct wlr_color_transform* // For DMA-BUF implicit sync interop, may be NULL VkSemaphore binary_semaphore; @@ -315,11 +316,12 @@ struct wlr_vk_renderer { // for blend->output subpass VkPipelineLayout output_pipe_layout; VkDescriptorSetLayout output_ds_srgb_layout; - VkDescriptorSetLayout output_ds_lut3d_layout; - VkSampler output_sampler_lut3d; + + VkDescriptorSetLayout ds_lut3d_layout; + VkSampler sampler_lut3d; // descriptor set indicating dummy 1x1x1 image, for use in the lut3d slot - VkDescriptorSet output_ds_lut3d_dummy; - struct wlr_vk_descriptor_pool *output_ds_lut3d_dummy_pool; + VkDescriptorSet ds_lut3d_dummy; + struct wlr_vk_descriptor_pool *ds_lut3d_dummy_pool; size_t last_output_pool_size; struct wl_list output_descriptor_pools; // wlr_vk_descriptor_pool.link @@ -374,6 +376,8 @@ struct wlr_vk_vert_pcr_data { struct wlr_vk_frag_texture_pcr_data { float matrix[4][4]; // only a 3x3 subset is used float alpha; + float lut_3d_offset; + float lut_3d_scale; }; struct wlr_vk_frag_output_pcr_data { @@ -488,6 +492,8 @@ bool vulkan_wait_command_buffer(struct wlr_vk_command_buffer *cb, struct wlr_vk_renderer *renderer); VkSemaphore vulkan_command_buffer_wait_sync_file(struct wlr_vk_renderer *renderer, struct wlr_vk_command_buffer *render_cb, size_t sem_index, int sync_file_fd); +bool vulkan_command_buffer_ref_color_transform(struct wlr_vk_command_buffer *cb, + struct wlr_color_transform *color_transform); bool vulkan_sync_render_pass_release(struct wlr_vk_renderer *renderer, struct wlr_vk_render_pass *pass); @@ -583,7 +589,7 @@ struct wlr_vk_color_transform { } lut_3d; float color_matrix[9]; - enum wlr_color_transfer_function inverse_eotf; + enum wlr_color_transfer_function eotf; }; void vk_color_transform_destroy(struct wlr_addon *addon); diff --git a/render/vulkan/pass.c b/render/vulkan/pass.c index e377c4214..ac9270939 100644 --- a/render/vulkan/pass.c +++ b/render/vulkan/pass.c @@ -258,7 +258,12 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { need_lut = transform->lut_3d.dim > 0; dim = need_lut ? transform->lut_3d.dim : 1; memcpy(matrix, transform->color_matrix, sizeof(matrix)); - tf = transform->inverse_eotf; + tf = transform->eotf; + } + if (need_lut) { + if (!vulkan_command_buffer_ref_color_transform(render_cb, pass->color_transform)) { + goto error; + } } if (pass->color_transform == NULL || need_lut) { wlr_matrix_identity(matrix); @@ -304,7 +309,7 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) { if (need_lut) { lut_ds = transform->lut_3d.ds; } else { - lut_ds = renderer->output_ds_lut3d_dummy; + lut_ds = renderer->ds_lut3d_dummy; } VkDescriptorSet ds[] = { render_buffer->two_pass.blend_descriptor_set, // set 0 @@ -772,6 +777,9 @@ static void render_pass_add_rect(struct wlr_render_pass *wlr_pass, pixman_region32_fini(&clip); } +static struct wlr_vk_color_transform *vk_color_transform_create( + struct wlr_vk_renderer *renderer, struct wlr_color_transform *transform, bool output); + static void render_pass_add_texture(struct wlr_render_pass *wlr_pass, const struct wlr_render_texture_options *options) { struct wlr_vk_render_pass *pass = get_render_pass(wlr_pass); @@ -817,37 +825,64 @@ static void render_pass_add_texture(struct wlr_render_pass *wlr_pass, }; encode_proj_matrix(matrix, vert_pcr_data.mat4); - enum wlr_color_transfer_function tf; + enum wlr_color_transfer_function tf = WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR; float color_matrix[9]; - if (!unwrap_texture_color_transform(options->color_transform, color_matrix, &tf)) - { - tf = WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR; + bool need_lut = false; + size_t dim = 1; + struct wlr_vk_color_transform *transform = NULL; + if (options->color_transform != NULL) { + transform = get_color_transform(options->color_transform, renderer); + if (transform == NULL) { + transform = vk_color_transform_create(renderer, options->color_transform, false); + if (transform == NULL) { + wlr_log(WLR_ERROR, "Failed to create color transform"); + pass->failed = true; + return; + } + } + need_lut = transform->lut_3d.dim > 0; + dim = need_lut ? transform->lut_3d.dim : 1; + memcpy(color_matrix, transform->color_matrix, sizeof(color_matrix)); + tf = transform->eotf; + } + if (need_lut) { + if (!vulkan_command_buffer_ref_color_transform(pass->command_buffer, + options->color_transform)) { + pass->failed = true; + return; + } + } + if (options->color_transform == NULL || need_lut) { wlr_matrix_identity(color_matrix); } bool srgb_image_view = false; enum wlr_vk_texture_transform tex_transform = 0; - switch (tf) { - case WLR_COLOR_TRANSFER_FUNCTION_SRGB: - if (texture->using_mutable_srgb) { + if (need_lut) { + tex_transform = WLR_VK_TEXTURE_TRANSFORM_LUT_3D; + } else { + switch (tf) { + case WLR_COLOR_TRANSFER_FUNCTION_SRGB: + if (texture->using_mutable_srgb) { + tex_transform = WLR_VK_TEXTURE_TRANSFORM_IDENTITY; + srgb_image_view = true; + } else { + tex_transform = WLR_VK_TEXTURE_TRANSFORM_SRGB; + } + break; + case WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR: tex_transform = WLR_VK_TEXTURE_TRANSFORM_IDENTITY; - srgb_image_view = true; - } else { - tex_transform = WLR_VK_TEXTURE_TRANSFORM_SRGB; + break; + case WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ: + tex_transform = WLR_VK_TEXTURE_TRANSFORM_ST2084_PQ; + break; + case WLR_COLOR_TRANSFER_FUNCTION_GAMMA22: + tex_transform = WLR_VK_TEXTURE_TRANSFORM_GAMMA22; + break; + case WLR_COLOR_TRANSFER_FUNCTION_BT1886: + tex_transform = WLR_VK_TEXTURE_TRANSFORM_BT1886; + break; } - break; - case WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR: - tex_transform = WLR_VK_TEXTURE_TRANSFORM_IDENTITY; - break; - case WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ: - tex_transform = WLR_VK_TEXTURE_TRANSFORM_ST2084_PQ; - break; - case WLR_COLOR_TRANSFER_FUNCTION_GAMMA22: - tex_transform = WLR_VK_TEXTURE_TRANSFORM_GAMMA22; - break; - case WLR_COLOR_TRANSFER_FUNCTION_BT1886: - tex_transform = WLR_VK_TEXTURE_TRANSFORM_BT1886; - break; } enum wlr_color_encoding color_encoding = options->color_encoding; @@ -891,13 +926,26 @@ static void render_pass_add_texture(struct wlr_render_pass *wlr_pass, struct wlr_vk_frag_texture_pcr_data frag_pcr_data = { .alpha = alpha, + .lut_3d_offset = 0.5f / dim, + .lut_3d_scale = (float)(dim - 1) / dim, }; encode_color_matrix(color_matrix, frag_pcr_data.matrix); bind_pipeline(pass, pipe->vk); + VkDescriptorSet lut_ds; + if (need_lut) { + lut_ds = transform->lut_3d.ds; + } else { + lut_ds = renderer->ds_lut3d_dummy; + } + VkDescriptorSet ds[] = { + view->ds, // set 0 + lut_ds, // set 1 + }; + size_t ds_len = sizeof(ds) / sizeof(ds[0]); vkCmdBindDescriptorSets(cb, VK_PIPELINE_BIND_POINT_GRAPHICS, - pipe->layout->vk, 0, 1, &view->ds, 0, NULL); + pipe->layout->vk, 0, ds_len, ds, 0, NULL); vkCmdPushConstants(cb, pipe->layout->vk, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(vert_pcr_data), &vert_pcr_data); @@ -1123,7 +1171,7 @@ static bool create_3d_lut_image(struct wlr_vk_renderer *renderer, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, VK_ACCESS_SHADER_READ_BIT); *ds_pool = vulkan_alloc_texture_ds(renderer, - renderer->output_ds_lut3d_layout, ds); + renderer->ds_lut3d_layout, ds); if (!*ds_pool) { wlr_log(WLR_ERROR, "Failed to allocate descriptor"); goto fail_imageview; @@ -1154,15 +1202,22 @@ fail_image: } static struct wlr_vk_color_transform *vk_color_transform_create( - struct wlr_vk_renderer *renderer, struct wlr_color_transform *transform) { + struct wlr_vk_renderer *renderer, struct wlr_color_transform *transform, + bool output) { struct wlr_vk_color_transform *vk_transform = calloc(1, sizeof(*vk_transform)); if (!vk_transform) { return NULL; } - bool need_lut = !unwrap_output_color_transform(transform, vk_transform->color_matrix, - &vk_transform->inverse_eotf); + bool need_lut; + if (output) { + need_lut = !unwrap_output_color_transform(transform, vk_transform->color_matrix, + &vk_transform->eotf); + } else { + need_lut = !unwrap_texture_color_transform(transform, vk_transform->color_matrix, + &vk_transform->eotf); + } if (need_lut) { vk_transform->lut_3d.dim = 33; @@ -1226,7 +1281,7 @@ struct wlr_vk_render_pass *vulkan_begin_render_pass(struct wlr_vk_renderer *rend if (options != NULL && options->color_transform != NULL && !get_color_transform(options->color_transform, renderer)) { /* Try to create a new color transform */ - if (!vk_color_transform_create(renderer, options->color_transform)) { + if (!vk_color_transform_create(renderer, options->color_transform, true)) { wlr_log(WLR_ERROR, "Failed to create color transform"); return NULL; } diff --git a/render/vulkan/renderer.c b/render/vulkan/renderer.c index 434ab4769..8904219ec 100644 --- a/render/vulkan/renderer.c +++ b/render/vulkan/renderer.c @@ -551,10 +551,12 @@ static void release_command_buffer_resources(struct wlr_vk_command_buffer *cb, wl_list_insert(&renderer->stage.buffers, &buf->link); } - if (cb->color_transform) { - wlr_color_transform_unref(cb->color_transform); - cb->color_transform = NULL; + struct wlr_color_transform **transform; + wl_array_for_each(transform, &cb->color_transforms) { + wlr_color_transform_unref(*transform); } + wl_array_release(&cb->color_transforms); + wl_array_init(&cb->color_transforms); } static struct wlr_vk_command_buffer *get_command_buffer( @@ -666,6 +668,16 @@ void vulkan_reset_command_buffer(struct wlr_vk_command_buffer *cb) { } } +bool vulkan_command_buffer_ref_color_transform(struct wlr_vk_command_buffer *cb, + struct wlr_color_transform *color_transform) { + struct wlr_color_transform **ref = wl_array_add(&cb->color_transforms, sizeof(*ref)); + if (ref == NULL) { + return false; + } + *ref = wlr_color_transform_ref(color_transform); + return true; +} + static void finish_render_buffer_out(struct wlr_vk_render_buffer_out *out, VkDevice dev) { vkDestroyFramebuffer(dev, out->framebuffer, NULL); @@ -1259,9 +1271,9 @@ static void vulkan_destroy(struct wlr_renderer *wlr_renderer) { vkDestroySemaphore(dev->dev, renderer->timeline_semaphore, NULL); vkDestroyPipelineLayout(dev->dev, renderer->output_pipe_layout, NULL); vkDestroyDescriptorSetLayout(dev->dev, renderer->output_ds_srgb_layout, NULL); - vkDestroyDescriptorSetLayout(dev->dev, renderer->output_ds_lut3d_layout, NULL); + vkDestroyDescriptorSetLayout(dev->dev, renderer->ds_lut3d_layout, NULL); vkDestroyCommandPool(dev->dev, renderer->command_pool, NULL); - vkDestroySampler(dev->dev, renderer->output_sampler_lut3d, NULL); + vkDestroySampler(dev->dev, renderer->sampler_lut3d, NULL); if (renderer->read_pixels_cache.initialized) { vkFreeMemory(dev->dev, renderer->read_pixels_cache.dst_img_memory, NULL); @@ -1680,10 +1692,15 @@ static bool init_tex_layouts(struct wlr_vk_renderer *renderer, }, }; + VkDescriptorSetLayout out_ds_layouts[] = { + *out_ds_layout, + renderer->ds_lut3d_layout, + }; + VkPipelineLayoutCreateInfo pl_info = { .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, - .setLayoutCount = 1, - .pSetLayouts = out_ds_layout, + .setLayoutCount = sizeof(out_ds_layouts) / sizeof(out_ds_layouts[0]), + .pSetLayouts = out_ds_layouts, .pushConstantRangeCount = sizeof(pc_ranges) / sizeof(pc_ranges[0]), .pPushConstantRanges = pc_ranges, }; @@ -1734,7 +1751,7 @@ static bool init_blend_to_output_layouts(struct wlr_vk_renderer *renderer) { }; res = vkCreateSampler(renderer->dev->dev, &sampler_create_info, NULL, - &renderer->output_sampler_lut3d); + &renderer->sampler_lut3d); if (res != VK_SUCCESS) { wlr_vk_error("vkCreateSampler", res); return false; @@ -1745,7 +1762,7 @@ static bool init_blend_to_output_layouts(struct wlr_vk_renderer *renderer) { .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .descriptorCount = 1, .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT, - .pImmutableSamplers = &renderer->output_sampler_lut3d, + .pImmutableSamplers = &renderer->sampler_lut3d, }; VkDescriptorSetLayoutCreateInfo ds_lut3d_info = { @@ -1755,7 +1772,7 @@ static bool init_blend_to_output_layouts(struct wlr_vk_renderer *renderer) { }; res = vkCreateDescriptorSetLayout(dev, &ds_lut3d_info, NULL, - &renderer->output_ds_lut3d_layout); + &renderer->ds_lut3d_layout); if (res != VK_SUCCESS) { wlr_vk_error("vkCreateDescriptorSetLayout", res); return false; @@ -1777,7 +1794,7 @@ static bool init_blend_to_output_layouts(struct wlr_vk_renderer *renderer) { VkDescriptorSetLayout out_ds_layouts[] = { renderer->output_ds_srgb_layout, - renderer->output_ds_lut3d_layout, + renderer->ds_lut3d_layout, }; VkPipelineLayoutCreateInfo pl_info = { @@ -2307,9 +2324,9 @@ static bool init_dummy_images(struct wlr_vk_renderer *renderer) { return false; } - renderer->output_ds_lut3d_dummy_pool = vulkan_alloc_texture_ds(renderer, - renderer->output_ds_lut3d_layout, &renderer->output_ds_lut3d_dummy); - if (!renderer->output_ds_lut3d_dummy_pool) { + renderer->ds_lut3d_dummy_pool = vulkan_alloc_texture_ds(renderer, + renderer->ds_lut3d_layout, &renderer->ds_lut3d_dummy); + if (!renderer->ds_lut3d_dummy_pool) { wlr_log(WLR_ERROR, "Failed to allocate descriptor"); return false; } @@ -2321,7 +2338,7 @@ static bool init_dummy_images(struct wlr_vk_renderer *renderer) { .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .descriptorCount = 1, .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, - .dstSet = renderer->output_ds_lut3d_dummy, + .dstSet = renderer->ds_lut3d_dummy, .pImageInfo = &ds_img_info, }; vkUpdateDescriptorSets(dev, 1, &ds_write, 0, NULL); diff --git a/render/vulkan/shaders/texture.frag b/render/vulkan/shaders/texture.frag index 03c24eceb..7743352cf 100644 --- a/render/vulkan/shaders/texture.frag +++ b/render/vulkan/shaders/texture.frag @@ -2,6 +2,8 @@ layout(set = 0, binding = 0) uniform sampler2D tex; +layout(set = 1, binding = 0) uniform sampler3D lut_3d; + layout(location = 0) in vec2 uv; layout(location = 0) out vec4 out_color; @@ -9,6 +11,8 @@ layout(location = 0) out vec4 out_color; layout(push_constant, row_major) uniform UBO { layout(offset = 80) mat4 matrix; float alpha; + float lut_3d_offset; + float lut_3d_scale; } data; layout (constant_id = 0) const int TEXTURE_TRANSFORM = 0; @@ -19,6 +23,7 @@ layout (constant_id = 0) const int TEXTURE_TRANSFORM = 0; #define TEXTURE_TRANSFORM_ST2084_PQ 2 #define TEXTURE_TRANSFORM_GAMMA22 3 #define TEXTURE_TRANSFORM_BT1886 4 +#define TEXTURE_TRANSFORM_LUT_3D 5 float srgb_channel_to_linear(float x) { return mix(x / 12.92, @@ -79,6 +84,10 @@ void main() { rgb = pow(rgb, vec3(2.2)); } else if (TEXTURE_TRANSFORM == TEXTURE_TRANSFORM_BT1886) { rgb = bt1886_color_to_linear(rgb); + } else if (TEXTURE_TRANSFORM == TEXTURE_TRANSFORM_LUT_3D) { + // Apply 3D LUT + vec3 pos = data.lut_3d_offset + rgb * data.lut_3d_scale; + rgb = texture(lut_3d, pos).rgb; } rgb = mat3(data.matrix) * rgb; From d98ff4553d9f21bf34fb3fda506c905eb2275a88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Poisot?= Date: Sun, 29 Mar 2026 13:00:44 +0000 Subject: [PATCH 5/5] scene: cache wlr_color_transform objects --- include/wlr/types/wlr_scene.h | 2 + types/scene/wlr_scene.c | 199 +++++++++++++++++++++++++--------- 2 files changed, 148 insertions(+), 53 deletions(-) diff --git a/include/wlr/types/wlr_scene.h b/include/wlr/types/wlr_scene.h index f6f97cfea..6002cee4e 100644 --- a/include/wlr/types/wlr_scene.h +++ b/include/wlr/types/wlr_scene.h @@ -117,6 +117,8 @@ struct wlr_scene { bool direct_scanout; bool calculate_visibility; bool highlight_transparent_region; + + struct wlr_scene_color_transform_cache *color_transform_cache; } WLR_PRIVATE; }; diff --git a/types/scene/wlr_scene.c b/types/scene/wlr_scene.c index 2a3b1f6bd..543012c73 100644 --- a/types/scene/wlr_scene.c +++ b/types/scene/wlr_scene.c @@ -97,6 +97,10 @@ static void scene_buffer_set_buffer(struct wlr_scene_buffer *scene_buffer, static void scene_buffer_set_texture(struct wlr_scene_buffer *scene_buffer, struct wlr_texture *texture); +static struct wlr_scene_color_transform_cache *scene_color_transform_cache_create(void); +static void scene_color_transform_cache_destroy( + struct wlr_scene_color_transform_cache *cache); + void wlr_scene_node_destroy(struct wlr_scene_node *node) { if (node == NULL) { return; @@ -135,6 +139,7 @@ void wlr_scene_node_destroy(struct wlr_scene_node *node) { wl_list_remove(&scene->linux_dmabuf_v1_destroy.link); wl_list_remove(&scene->gamma_control_manager_v1_destroy.link); wl_list_remove(&scene->gamma_control_manager_v1_set_gamma.link); + scene_color_transform_cache_destroy(scene->color_transform_cache); } else { assert(node->parent); } @@ -166,6 +171,12 @@ struct wlr_scene *wlr_scene_create(void) { return NULL; } + scene->color_transform_cache = scene_color_transform_cache_create(); + if (scene->color_transform_cache == NULL) { + free(scene); + return NULL; + } + scene_tree_init(&scene->tree, NULL); wl_list_init(&scene->outputs); @@ -1414,61 +1425,137 @@ static float get_luminance_multiplier(const struct wlr_color_luminances *src_lum return (dst_lum->reference / src_lum->reference) * (src_lum->max / dst_lum->max); } -static struct wlr_color_transform *scene_texture_to_blend_space( - struct wlr_scene_buffer *source, enum wlr_color_named_primaries dest_primaries, - const struct wlr_color_luminances *dest_luminance) { - struct wlr_color_transform *color_matrix = NULL; - struct wlr_color_transform *eotf = NULL; - struct wlr_color_transform *combined = NULL; +struct scene_color_transform_cache_key { + enum wlr_color_transfer_function transfer_function; + enum wlr_color_named_primaries source_primaries; + enum wlr_color_named_primaries dest_primaries; + float luminance_multiplier; +}; - enum wlr_color_transfer_function source_tf = WLR_COLOR_TRANSFER_FUNCTION_GAMMA22; - if (source->transfer_function != 0) { - source_tf = source->transfer_function; +struct scene_color_transform_cache_entry { + bool has_value; + struct scene_color_transform_cache_key key; + struct wlr_color_transform *color_transform; +}; + +#define COLOR_TRANSFORM_CACHE_ENTRIES 8 + +struct wlr_scene_color_transform_cache { + int insert_at; + struct scene_color_transform_cache_entry entries[COLOR_TRANSFORM_CACHE_ENTRIES]; +}; + +static struct wlr_scene_color_transform_cache *scene_color_transform_cache_create(void) { + struct wlr_scene_color_transform_cache *result; + result = calloc(1, sizeof(*result)); + return result; +} + +static void scene_color_transform_cache_destroy( + struct wlr_scene_color_transform_cache *cache) { + for (int i = 0; i < COLOR_TRANSFORM_CACHE_ENTRIES; ++i) { + if (cache->entries[i].has_value) { + wlr_color_transform_unref(cache->entries[i].color_transform); + } } - eotf = wlr_color_transform_init_eotf_to_linear(source_tf); - if (eotf == NULL) { - goto cleanup_transforms; + free(cache); +} + +static struct wlr_color_transform *scene_color_transform_cache_get( + struct wlr_scene_color_transform_cache *cache, + struct scene_color_transform_cache_key *key) { + struct wlr_color_transform *result = NULL; + + for (int i = 0; i < COLOR_TRANSFORM_CACHE_ENTRIES; ++i) { + struct scene_color_transform_cache_entry *entry = &cache->entries[i]; + if (entry->has_value + && entry->key.transfer_function == key->transfer_function + && entry->key.source_primaries == key->source_primaries + && entry->key.dest_primaries == key->dest_primaries + && entry->key.luminance_multiplier == key->luminance_multiplier) { + result = wlr_color_transform_ref(entry->color_transform); + break; + } + } + + if (result == NULL) { + struct scene_color_transform_cache_entry *entry = &cache->entries[cache->insert_at]; + wlr_color_transform_unref(entry->color_transform); + entry->has_value = false; + + struct wlr_color_transform *color_matrix = NULL; + struct wlr_color_transform *eotf = NULL; + + eotf = wlr_color_transform_init_eotf_to_linear(key->transfer_function); + if (eotf == NULL) { + goto cleanup_transforms; + } + + float matrix[9]; + if (key->source_primaries != key->dest_primaries) { + struct wlr_color_primaries primaries; + wlr_color_primaries_from_named(&primaries, key->source_primaries); + struct wlr_color_primaries primaries_blend; + wlr_color_primaries_from_named(&primaries_blend, key->dest_primaries); + wlr_color_primaries_transform_absolute_colorimetric(&primaries, &primaries_blend, matrix); + } else { + wlr_matrix_identity(matrix); + } + + for (int i = 0; i < 9; ++i) { + matrix[i] *= key->luminance_multiplier; + } + color_matrix = wlr_color_transform_init_matrix(matrix); + if (color_matrix == NULL) { + goto cleanup_transforms; + } + + struct wlr_color_transform *transforms[] = { + eotf, + color_matrix, + }; + const size_t transforms_len = sizeof(transforms) / sizeof(transforms[0]); + if (!color_transform_compose(&result, transforms, transforms_len)) { + goto cleanup_transforms; + } + + entry->color_transform = result != NULL ? wlr_color_transform_ref(result) : NULL; + entry->key = *key; + entry->has_value = true; + cache->insert_at = (cache->insert_at + 1) % COLOR_TRANSFORM_CACHE_ENTRIES; + +cleanup_transforms: + wlr_color_transform_unref(eotf); + wlr_color_transform_unref(color_matrix); + } + + return result; +} + +static struct wlr_color_transform *scene_texture_to_blend_space( + struct wlr_scene_buffer *source, struct wlr_scene_output *scene_output, + enum wlr_color_named_primaries dest_primaries, + const struct wlr_color_luminances *dest_luminance) { + + struct scene_color_transform_cache_key key = { + .transfer_function = WLR_COLOR_TRANSFER_FUNCTION_GAMMA22, + .source_primaries = WLR_COLOR_NAMED_PRIMARIES_SRGB, + .dest_primaries = dest_primaries, + }; + + if (source->transfer_function != 0) { + key.transfer_function = source->transfer_function; + } + if (source->primaries != 0) { + key.source_primaries = source->primaries; } struct wlr_color_luminances source_lum; - wlr_color_transfer_function_get_default_luminance(source_tf, &source_lum); - float luminance_multiplier = get_luminance_multiplier(&source_lum, dest_luminance); + wlr_color_transfer_function_get_default_luminance(key.transfer_function, &source_lum); + key.luminance_multiplier = get_luminance_multiplier(&source_lum, dest_luminance); - float matrix[9]; - - enum wlr_color_named_primaries source_primaries = WLR_COLOR_NAMED_PRIMARIES_SRGB; - if (source->primaries != 0) { - source_primaries = source->primaries; - } - if (source_primaries != dest_primaries) { - struct wlr_color_primaries primaries; - wlr_color_primaries_from_named(&primaries, source_primaries); - struct wlr_color_primaries primaries_blend; - wlr_color_primaries_from_named(&primaries_blend, dest_primaries); - wlr_color_primaries_transform_absolute_colorimetric(&primaries, &primaries_blend, matrix); - } else { - wlr_matrix_identity(matrix); - } - - for (int i = 0; i < 9; ++i) { - matrix[i] *= luminance_multiplier; - } - color_matrix = wlr_color_transform_init_matrix(matrix); - if (color_matrix == NULL) { - goto cleanup_transforms; - } - - struct wlr_color_transform *transforms[] = { - eotf, - color_matrix, - }; - const size_t transforms_len = sizeof(transforms) / sizeof(transforms[0]); - color_transform_compose(&combined, transforms, transforms_len); - -cleanup_transforms: - wlr_color_transform_unref(eotf); - wlr_color_transform_unref(color_matrix); - return combined; + struct wlr_scene *scene = scene_output->scene; + return scene_color_transform_cache_get(scene->color_transform_cache, &key); } static struct wlr_render_color scene_color_transform_premultiplied( @@ -1574,8 +1661,8 @@ static void scene_entry_render(struct render_list_entry *entry, const struct ren struct wlr_color_luminances srgb_lum; wlr_color_transfer_function_get_default_luminance(WLR_COLOR_TRANSFER_FUNCTION_SRGB, &srgb_lum); - source_to_blend = scene_texture_to_blend_space( - scene_buffer, WLR_COLOR_NAMED_PRIMARIES_SRGB, &srgb_lum); + source_to_blend = scene_texture_to_blend_space(scene_buffer, data->output, + WLR_COLOR_NAMED_PRIMARIES_SRGB, &srgb_lum); } wlr_render_pass_add_texture(data->render_pass, &(struct wlr_render_texture_options) { @@ -2561,8 +2648,14 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output, } } - render_data.default_color_transform = wlr_color_transform_init_eotf_to_linear( - WLR_COLOR_TRANSFER_FUNCTION_GAMMA22); + render_data.default_color_transform = scene_color_transform_cache_get( + scene_output->scene->color_transform_cache, + &(struct scene_color_transform_cache_key) { + .transfer_function = WLR_COLOR_TRANSFER_FUNCTION_GAMMA22, + .source_primaries = WLR_COLOR_NAMED_PRIMARIES_SRGB, + .dest_primaries = WLR_COLOR_NAMED_PRIMARIES_SRGB, + .luminance_multiplier = 1.0, + }); } scene_output->in_point++;