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] 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,