Merge branch 'render' into 'master'

render: general color_transform for wlr_render_pass_add_texture()

See merge request wlroots/wlroots!5260
This commit is contained in:
Félix Poisot 2026-04-12 10:42:27 +00:00
commit b36a839457
12 changed files with 544 additions and 151 deletions

View file

@ -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;
}

View file

@ -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 <wlr/config.h>
@ -96,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;
@ -134,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);
}
@ -165,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);
@ -319,6 +331,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,
@ -1411,6 +1425,161 @@ 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);
}
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;
};
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);
}
}
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(key.transfer_function, &source_lum);
key.luminance_multiplier = get_luminance_multiplier(&source_lum, dest_luminance);
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(
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;
@ -1450,12 +1619,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;
@ -1466,13 +1634,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;
@ -1489,17 +1656,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, data->output,
WLR_COLOR_NAMED_PRIMARIES_SRGB, &srgb_lum);
}
wlr_render_pass_add_texture(data->render_pass, &(struct wlr_render_texture_options) {
.texture = texture,
@ -1512,15 +1676,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,
@ -1532,7 +1696,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,
});
}
@ -2468,17 +2633,29 @@ 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 = 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++;
@ -2491,6 +2668,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;
}
@ -2575,7 +2753,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,
});
}
@ -2586,6 +2765,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
@ -2595,6 +2775,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,