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

@ -11,6 +11,7 @@ enum wlr_color_transform_type {
COLOR_TRANSFORM_LUT_3X1D, COLOR_TRANSFORM_LUT_3X1D,
COLOR_TRANSFORM_MATRIX, COLOR_TRANSFORM_MATRIX,
COLOR_TRANSFORM_PIPELINE, COLOR_TRANSFORM_PIPELINE,
COLOR_TRANSFORM_EOTF,
}; };
struct wlr_color_transform { struct wlr_color_transform {
@ -20,6 +21,12 @@ struct wlr_color_transform {
enum wlr_color_transform_type type; 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_inverse_eotf {
struct wlr_color_transform base; 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, void color_transform_lcms2_eval(struct wlr_color_transform_lcms2 *tr,
float out[static 3], const float in[static 3]); 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. * Gets a wlr_color_transform_inverse_eotf from a generic wlr_color_transform.
* Asserts that the base type is COLOR_TRANSFORM_INVERSE_EOTF * Asserts that the base type is COLOR_TRANSFORM_INVERSE_EOTF

View file

@ -168,6 +168,7 @@ enum wlr_vk_texture_transform {
WLR_VK_TEXTURE_TRANSFORM_ST2084_PQ = 2, WLR_VK_TEXTURE_TRANSFORM_ST2084_PQ = 2,
WLR_VK_TEXTURE_TRANSFORM_GAMMA22 = 3, WLR_VK_TEXTURE_TRANSFORM_GAMMA22 = 3,
WLR_VK_TEXTURE_TRANSFORM_BT1886 = 4, WLR_VK_TEXTURE_TRANSFORM_BT1886 = 4,
WLR_VK_TEXTURE_TRANSFORM_LUT_3D = 5,
}; };
enum wlr_vk_shader_source { enum wlr_vk_shader_source {
@ -286,8 +287,8 @@ struct wlr_vk_command_buffer {
struct wl_list destroy_textures; // wlr_vk_texture.destroy_link struct wl_list destroy_textures; // wlr_vk_texture.destroy_link
// Staging shared buffers to release after the command buffer completes // Staging shared buffers to release after the command buffer completes
struct wl_list stage_buffers; // wlr_vk_shared_buffer.link struct wl_list stage_buffers; // wlr_vk_shared_buffer.link
// Color transform to unref after the command buffer completes // Color transforms to unref after the command buffer completes
struct wlr_color_transform *color_transform; struct wl_array color_transforms; // struct wlr_color_transform*
// For DMA-BUF implicit sync interop, may be NULL // For DMA-BUF implicit sync interop, may be NULL
VkSemaphore binary_semaphore; VkSemaphore binary_semaphore;
@ -315,11 +316,12 @@ struct wlr_vk_renderer {
// for blend->output subpass // for blend->output subpass
VkPipelineLayout output_pipe_layout; VkPipelineLayout output_pipe_layout;
VkDescriptorSetLayout output_ds_srgb_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 // descriptor set indicating dummy 1x1x1 image, for use in the lut3d slot
VkDescriptorSet output_ds_lut3d_dummy; VkDescriptorSet ds_lut3d_dummy;
struct wlr_vk_descriptor_pool *output_ds_lut3d_dummy_pool; struct wlr_vk_descriptor_pool *ds_lut3d_dummy_pool;
size_t last_output_pool_size; size_t last_output_pool_size;
struct wl_list output_descriptor_pools; // wlr_vk_descriptor_pool.link struct wl_list output_descriptor_pools; // wlr_vk_descriptor_pool.link
@ -374,7 +376,8 @@ struct wlr_vk_vert_pcr_data {
struct wlr_vk_frag_texture_pcr_data { struct wlr_vk_frag_texture_pcr_data {
float matrix[4][4]; // only a 3x3 subset is used float matrix[4][4]; // only a 3x3 subset is used
float alpha; float alpha;
float luminance_multiplier; float lut_3d_offset;
float lut_3d_scale;
}; };
struct wlr_vk_frag_output_pcr_data { struct wlr_vk_frag_output_pcr_data {
@ -489,6 +492,8 @@ bool vulkan_wait_command_buffer(struct wlr_vk_command_buffer *cb,
struct wlr_vk_renderer *renderer); struct wlr_vk_renderer *renderer);
VkSemaphore vulkan_command_buffer_wait_sync_file(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); 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, bool vulkan_sync_render_pass_release(struct wlr_vk_renderer *renderer,
struct wlr_vk_render_pass *pass); struct wlr_vk_render_pass *pass);
@ -584,7 +589,7 @@ struct wlr_vk_color_transform {
} lut_3d; } lut_3d;
float color_matrix[9]; 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); void vk_color_transform_destroy(struct wlr_addon *addon);

View file

@ -127,6 +127,13 @@ struct wlr_color_transform;
struct wlr_color_transform *wlr_color_transform_init_linear_to_icc( struct wlr_color_transform *wlr_color_transform_init_linear_to_icc(
const void *data, size_t size); 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 * Initialize a color transformation to apply EOTF¹ encoding. Returns
* NULL on failure. * NULL on failure.

View file

@ -31,9 +31,7 @@ struct wlr_render_timer;
struct wlr_buffer_pass_options { struct wlr_buffer_pass_options {
/* Timer to measure the duration of the render pass */ /* Timer to measure the duration of the render pass */
struct wlr_render_timer *timer; struct wlr_render_timer *timer;
/* Color transform to apply to the output of the render pass. /* 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) */
struct wlr_color_transform *color_transform; struct wlr_color_transform *color_transform;
/* Signal a timeline synchronization point when the render pass completes. /* Signal a timeline synchronization point when the render pass completes.
@ -101,16 +99,12 @@ struct wlr_render_texture_options {
enum wlr_scale_filter_mode filter_mode; enum wlr_scale_filter_mode filter_mode;
/* Blend mode */ /* Blend mode */
enum wlr_render_blend_mode blend_mode; enum wlr_render_blend_mode blend_mode;
/* Transfer function the source texture is encoded with */ /* Applied to convert from source texture to blend space */
enum wlr_color_transfer_function transfer_function; struct wlr_color_transform *color_transform;
/* Primaries describing the color volume of the source texture */
const struct wlr_color_primaries *primaries;
/* Color encoding of the source texture for YCbCr conversion to RGB */ /* Color encoding of the source texture for YCbCr conversion to RGB */
enum wlr_color_encoding color_encoding; enum wlr_color_encoding color_encoding;
/* Color range of the source texture */ /* Color range of the source texture */
enum wlr_color_range color_range; enum wlr_color_range color_range;
/* Default: 1.0 */
const float *luminance_multiplier;
/* Wait for a timeline synchronization point before texturing. /* Wait for a timeline synchronization point before texturing.
* *

View file

@ -53,7 +53,8 @@ struct wlr_output_cursor {
struct { struct {
struct wl_listener renderer_destroy; 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; } WLR_PRIVATE;
}; };

View file

@ -117,6 +117,8 @@ struct wlr_scene {
bool direct_scanout; bool direct_scanout;
bool calculate_visibility; bool calculate_visibility;
bool highlight_transparent_region; bool highlight_transparent_region;
struct wlr_scene_color_transform_cache *color_transform_cache;
} WLR_PRIVATE; } WLR_PRIVATE;
}; };

View file

@ -29,6 +29,17 @@ void wlr_color_transform_init(struct wlr_color_transform *tr, enum wlr_color_tra
wlr_addon_set_init(&tr->addons); 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( struct wlr_color_transform *wlr_color_transform_init_linear_to_inverse_eotf(
enum wlr_color_transfer_function tf) { enum wlr_color_transfer_function tf) {
struct wlr_color_transform_inverse_eotf *tx = calloc(1, sizeof(*tx)); 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) { switch (tr->type) {
case COLOR_TRANSFORM_INVERSE_EOTF: case COLOR_TRANSFORM_INVERSE_EOTF:
case COLOR_TRANSFORM_MATRIX: case COLOR_TRANSFORM_MATRIX:
case COLOR_TRANSFORM_EOTF:
break; break;
case COLOR_TRANSFORM_LCMS2: case COLOR_TRANSFORM_LCMS2:
color_transform_lcms2_finish(color_transform_lcms2_from_base(tr)); 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_inverse_eotf *wlr_color_transform_inverse_eotf_from_base(
struct wlr_color_transform *tr) { struct wlr_color_transform *tr) {
assert(tr->type == COLOR_TRANSFORM_INVERSE_EOTF); 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; 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) { static float srgb_eval_inverse_eotf(float x) {
// See https://www.w3.org/Graphics/Color/srgb // See https://www.w3.org/Graphics/Color/srgb
if (x <= 0.0031308) { 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) { static float st2084_pq_eval_inverse_eotf(float x) {
// H.273 TransferCharacteristics code point 16 // H.273 TransferCharacteristics code point 16
float c1 = 0.8359375; 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); 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) { static float bt1886_eval_inverse_eotf(float x) {
float lb = powf(0.0001, 1.0 / 2.4); float lb = powf(0.0001, 1.0 / 2.4);
float lw = powf(1.0, 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; 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( static float transfer_function_eval_inverse_eotf(
enum wlr_color_transfer_function tf, float x) { enum wlr_color_transfer_function tf, float x) {
switch (tf) { switch (tf) {
@ -205,6 +271,14 @@ static float transfer_function_eval_inverse_eotf(
abort(); // unreachable 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( static void color_transform_inverse_eotf_eval(
struct wlr_color_transform_inverse_eotf *tr, struct wlr_color_transform_inverse_eotf *tr,
float out[static 3], const float in[static 3]) { 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)); memcpy(out, color, sizeof(color));
break; break;
case COLOR_TRANSFORM_EOTF:
color_transform_eotf_eval(wlr_color_transform_eotf_from_base(tr), out, in);
break;
} }
} }

View file

@ -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]) { static void encode_proj_matrix(const float mat3[9], float mat4[4][4]) {
float result[4][4] = { float result[4][4] = {
{ mat3[0], mat3[1], 0, mat3[2] }, { mat3[0], mat3[1], 0, mat3[2] },
@ -134,11 +126,11 @@ static bool render_pass_wait_render_buffer(struct wlr_vk_render_pass *pass,
return true; 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) { float matrix[static 9], enum wlr_color_transfer_function *tf) {
if (transform == NULL) { if (transform == NULL) {
wlr_matrix_identity(matrix); wlr_matrix_identity(matrix);
*tf = WLR_COLOR_TRANSFER_FUNCTION_GAMMA22; *tf = WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR;
return true; return true;
} }
struct wlr_color_transform_inverse_eotf *eotf; struct wlr_color_transform_inverse_eotf *eotf;
@ -168,6 +160,48 @@ static bool unwrap_color_transform(struct wlr_color_transform *transform,
*tf = eotf->tf; *tf = eotf->tf;
return true; return true;
case COLOR_TRANSFORM_LCMS2: case COLOR_TRANSFORM_LCMS2:
case COLOR_TRANSFORM_LUT_3X1D:
case COLOR_TRANSFORM_EOTF:
return false;
}
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_EXT_LINEAR;
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: case COLOR_TRANSFORM_LUT_3X1D:
return false; return false;
} }
@ -214,7 +248,7 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) {
encode_proj_matrix(final_matrix, vert_pcr_data.mat4); encode_proj_matrix(final_matrix, vert_pcr_data.mat4);
float matrix[9]; 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; bool need_lut = false;
size_t dim = 1; size_t dim = 1;
struct wlr_vk_color_transform *transform = NULL; struct wlr_vk_color_transform *transform = NULL;
@ -224,7 +258,12 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) {
need_lut = transform->lut_3d.dim > 0; need_lut = transform->lut_3d.dim > 0;
dim = need_lut ? transform->lut_3d.dim : 1; dim = need_lut ? transform->lut_3d.dim : 1;
memcpy(matrix, transform->color_matrix, sizeof(matrix)); 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) { if (pass->color_transform == NULL || need_lut) {
wlr_matrix_identity(matrix); wlr_matrix_identity(matrix);
@ -270,7 +309,7 @@ static bool render_pass_submit(struct wlr_render_pass *wlr_pass) {
if (need_lut) { if (need_lut) {
lut_ds = transform->lut_3d.ds; lut_ds = transform->lut_3d.ds;
} else { } else {
lut_ds = renderer->output_ds_lut3d_dummy; lut_ds = renderer->ds_lut3d_dummy;
} }
VkDescriptorSet ds[] = { VkDescriptorSet ds[] = {
render_buffer->two_pass.blend_descriptor_set, // set 0 render_buffer->two_pass.blend_descriptor_set, // set 0
@ -646,16 +685,11 @@ static void render_pass_add_rect(struct wlr_render_pass *wlr_pass,
struct wlr_vk_render_pass *pass = get_render_pass(wlr_pass); struct wlr_vk_render_pass *pass = get_render_pass(wlr_pass);
VkCommandBuffer cb = pass->command_buffer->vk; 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[] = { float linear_color[] = {
color_to_linear_premult(options->color.r, options->color.a), options->color.r,
color_to_linear_premult(options->color.g, options->color.a), options->color.g,
color_to_linear_premult(options->color.b, options->color.a), options->color.b,
options->color.a, // no conversion for alpha options->color.a,
}; };
pixman_region32_t clip; pixman_region32_t clip;
@ -743,6 +777,9 @@ static void render_pass_add_rect(struct wlr_render_pass *wlr_pass,
pixman_region32_fini(&clip); 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, static void render_pass_add_texture(struct wlr_render_pass *wlr_pass,
const struct wlr_render_texture_options *options) { const struct wlr_render_texture_options *options) {
struct wlr_vk_render_pass *pass = get_render_pass(wlr_pass); struct wlr_vk_render_pass *pass = get_render_pass(wlr_pass);
@ -788,13 +825,42 @@ static void render_pass_add_texture(struct wlr_render_pass *wlr_pass,
}; };
encode_proj_matrix(matrix, vert_pcr_data.mat4); encode_proj_matrix(matrix, vert_pcr_data.mat4);
enum wlr_color_transfer_function tf = options->transfer_function; enum wlr_color_transfer_function tf = WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR;
if (tf == 0) { float color_matrix[9];
tf = WLR_COLOR_TRANSFER_FUNCTION_GAMMA22; 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; bool srgb_image_view = false;
enum wlr_vk_texture_transform tex_transform = 0; enum wlr_vk_texture_transform tex_transform = 0;
if (need_lut) {
tex_transform = WLR_VK_TEXTURE_TRANSFORM_LUT_3D;
} else {
switch (tf) { switch (tf) {
case WLR_COLOR_TRANSFER_FUNCTION_SRGB: case WLR_COLOR_TRANSFER_FUNCTION_SRGB:
if (texture->using_mutable_srgb) { if (texture->using_mutable_srgb) {
@ -817,6 +883,7 @@ static void render_pass_add_texture(struct wlr_render_pass *wlr_pass,
tex_transform = WLR_VK_TEXTURE_TRANSFORM_BT1886; tex_transform = WLR_VK_TEXTURE_TRANSFORM_BT1886;
break; break;
} }
}
enum wlr_color_encoding color_encoding = options->color_encoding; enum wlr_color_encoding color_encoding = options->color_encoding;
bool is_ycbcr = vulkan_format_is_ycbcr(texture->format); bool is_ycbcr = vulkan_format_is_ycbcr(texture->format);
@ -857,32 +924,28 @@ static void render_pass_add_texture(struct wlr_render_pass *wlr_pass,
return; 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 = { struct wlr_vk_frag_texture_pcr_data frag_pcr_data = {
.alpha = alpha, .alpha = alpha,
.luminance_multiplier = luminance_multiplier, .lut_3d_offset = 0.5f / dim,
.lut_3d_scale = (float)(dim - 1) / dim,
}; };
encode_color_matrix(color_matrix, frag_pcr_data.matrix); encode_color_matrix(color_matrix, frag_pcr_data.matrix);
bind_pipeline(pass, pipe->vk); 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, 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, vkCmdPushConstants(cb, pipe->layout->vk,
VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(vert_pcr_data), &vert_pcr_data); VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(vert_pcr_data), &vert_pcr_data);
@ -1108,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); VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, VK_ACCESS_SHADER_READ_BIT);
*ds_pool = vulkan_alloc_texture_ds(renderer, *ds_pool = vulkan_alloc_texture_ds(renderer,
renderer->output_ds_lut3d_layout, ds); renderer->ds_lut3d_layout, ds);
if (!*ds_pool) { if (!*ds_pool) {
wlr_log(WLR_ERROR, "Failed to allocate descriptor"); wlr_log(WLR_ERROR, "Failed to allocate descriptor");
goto fail_imageview; goto fail_imageview;
@ -1139,15 +1202,22 @@ fail_image:
} }
static struct wlr_vk_color_transform *vk_color_transform_create( 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 = struct wlr_vk_color_transform *vk_transform =
calloc(1, sizeof(*vk_transform)); calloc(1, sizeof(*vk_transform));
if (!vk_transform) { if (!vk_transform) {
return NULL; return NULL;
} }
bool need_lut = !unwrap_color_transform(transform, vk_transform->color_matrix, bool need_lut;
&vk_transform->inverse_eotf); 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) { if (need_lut) {
vk_transform->lut_3d.dim = 33; vk_transform->lut_3d.dim = 33;
@ -1190,7 +1260,7 @@ struct wlr_vk_render_pass *vulkan_begin_render_pass(struct wlr_vk_renderer *rend
} }
} else { } else {
// This is the default when unspecified // 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; bool using_linear_pathway = inv_eotf == WLR_COLOR_TRANSFER_FUNCTION_EXT_LINEAR;
@ -1211,7 +1281,7 @@ struct wlr_vk_render_pass *vulkan_begin_render_pass(struct wlr_vk_renderer *rend
if (options != NULL && options->color_transform != NULL && if (options != NULL && options->color_transform != NULL &&
!get_color_transform(options->color_transform, renderer)) { !get_color_transform(options->color_transform, renderer)) {
/* Try to create a new color transform */ /* 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"); wlr_log(WLR_ERROR, "Failed to create color transform");
return NULL; return NULL;
} }

View file

@ -551,10 +551,12 @@ static void release_command_buffer_resources(struct wlr_vk_command_buffer *cb,
wl_list_insert(&renderer->stage.buffers, &buf->link); wl_list_insert(&renderer->stage.buffers, &buf->link);
} }
if (cb->color_transform) { struct wlr_color_transform **transform;
wlr_color_transform_unref(cb->color_transform); wl_array_for_each(transform, &cb->color_transforms) {
cb->color_transform = NULL; 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( 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, static void finish_render_buffer_out(struct wlr_vk_render_buffer_out *out,
VkDevice dev) { VkDevice dev) {
vkDestroyFramebuffer(dev, out->framebuffer, NULL); 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); vkDestroySemaphore(dev->dev, renderer->timeline_semaphore, NULL);
vkDestroyPipelineLayout(dev->dev, renderer->output_pipe_layout, NULL); vkDestroyPipelineLayout(dev->dev, renderer->output_pipe_layout, NULL);
vkDestroyDescriptorSetLayout(dev->dev, renderer->output_ds_srgb_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); 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) { if (renderer->read_pixels_cache.initialized) {
vkFreeMemory(dev->dev, renderer->read_pixels_cache.dst_img_memory, NULL); 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 = { VkPipelineLayoutCreateInfo pl_info = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
.setLayoutCount = 1, .setLayoutCount = sizeof(out_ds_layouts) / sizeof(out_ds_layouts[0]),
.pSetLayouts = out_ds_layout, .pSetLayouts = out_ds_layouts,
.pushConstantRangeCount = sizeof(pc_ranges) / sizeof(pc_ranges[0]), .pushConstantRangeCount = sizeof(pc_ranges) / sizeof(pc_ranges[0]),
.pPushConstantRanges = pc_ranges, .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, res = vkCreateSampler(renderer->dev->dev, &sampler_create_info, NULL,
&renderer->output_sampler_lut3d); &renderer->sampler_lut3d);
if (res != VK_SUCCESS) { if (res != VK_SUCCESS) {
wlr_vk_error("vkCreateSampler", res); wlr_vk_error("vkCreateSampler", res);
return false; 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, .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
.descriptorCount = 1, .descriptorCount = 1,
.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT, .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT,
.pImmutableSamplers = &renderer->output_sampler_lut3d, .pImmutableSamplers = &renderer->sampler_lut3d,
}; };
VkDescriptorSetLayoutCreateInfo ds_lut3d_info = { 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, res = vkCreateDescriptorSetLayout(dev, &ds_lut3d_info, NULL,
&renderer->output_ds_lut3d_layout); &renderer->ds_lut3d_layout);
if (res != VK_SUCCESS) { if (res != VK_SUCCESS) {
wlr_vk_error("vkCreateDescriptorSetLayout", res); wlr_vk_error("vkCreateDescriptorSetLayout", res);
return false; return false;
@ -1777,7 +1794,7 @@ static bool init_blend_to_output_layouts(struct wlr_vk_renderer *renderer) {
VkDescriptorSetLayout out_ds_layouts[] = { VkDescriptorSetLayout out_ds_layouts[] = {
renderer->output_ds_srgb_layout, renderer->output_ds_srgb_layout,
renderer->output_ds_lut3d_layout, renderer->ds_lut3d_layout,
}; };
VkPipelineLayoutCreateInfo pl_info = { VkPipelineLayoutCreateInfo pl_info = {
@ -2307,9 +2324,9 @@ static bool init_dummy_images(struct wlr_vk_renderer *renderer) {
return false; return false;
} }
renderer->output_ds_lut3d_dummy_pool = vulkan_alloc_texture_ds(renderer, renderer->ds_lut3d_dummy_pool = vulkan_alloc_texture_ds(renderer,
renderer->output_ds_lut3d_layout, &renderer->output_ds_lut3d_dummy); renderer->ds_lut3d_layout, &renderer->ds_lut3d_dummy);
if (!renderer->output_ds_lut3d_dummy_pool) { if (!renderer->ds_lut3d_dummy_pool) {
wlr_log(WLR_ERROR, "Failed to allocate descriptor"); wlr_log(WLR_ERROR, "Failed to allocate descriptor");
return false; return false;
} }
@ -2321,7 +2338,7 @@ static bool init_dummy_images(struct wlr_vk_renderer *renderer) {
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
.descriptorCount = 1, .descriptorCount = 1,
.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
.dstSet = renderer->output_ds_lut3d_dummy, .dstSet = renderer->ds_lut3d_dummy,
.pImageInfo = &ds_img_info, .pImageInfo = &ds_img_info,
}; };
vkUpdateDescriptorSets(dev, 1, &ds_write, 0, NULL); vkUpdateDescriptorSets(dev, 1, &ds_write, 0, NULL);

View file

@ -2,6 +2,8 @@
layout(set = 0, binding = 0) uniform sampler2D tex; 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) in vec2 uv;
layout(location = 0) out vec4 out_color; layout(location = 0) out vec4 out_color;
@ -9,7 +11,8 @@ layout(location = 0) out vec4 out_color;
layout(push_constant, row_major) uniform UBO { layout(push_constant, row_major) uniform UBO {
layout(offset = 80) mat4 matrix; layout(offset = 80) mat4 matrix;
float alpha; float alpha;
float luminance_multiplier; float lut_3d_offset;
float lut_3d_scale;
} data; } data;
layout (constant_id = 0) const int TEXTURE_TRANSFORM = 0; layout (constant_id = 0) const int TEXTURE_TRANSFORM = 0;
@ -20,6 +23,7 @@ layout (constant_id = 0) const int TEXTURE_TRANSFORM = 0;
#define TEXTURE_TRANSFORM_ST2084_PQ 2 #define TEXTURE_TRANSFORM_ST2084_PQ 2
#define TEXTURE_TRANSFORM_GAMMA22 3 #define TEXTURE_TRANSFORM_GAMMA22 3
#define TEXTURE_TRANSFORM_BT1886 4 #define TEXTURE_TRANSFORM_BT1886 4
#define TEXTURE_TRANSFORM_LUT_3D 5
float srgb_channel_to_linear(float x) { float srgb_channel_to_linear(float x) {
return mix(x / 12.92, return mix(x / 12.92,
@ -80,10 +84,12 @@ void main() {
rgb = pow(rgb, vec3(2.2)); rgb = pow(rgb, vec3(2.2));
} else if (TEXTURE_TRANSFORM == TEXTURE_TRANSFORM_BT1886) { } else if (TEXTURE_TRANSFORM == TEXTURE_TRANSFORM_BT1886) {
rgb = bt1886_color_to_linear(rgb); 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 *= data.luminance_multiplier;
rgb = mat3(data.matrix) * rgb; rgb = mat3(data.matrix) * rgb;
// Back to pre-multiplied alpha // Back to pre-multiplied alpha

View file

@ -256,7 +256,7 @@ static struct wlr_buffer *render_cursor_buffer(struct wlr_output_cursor *cursor)
buffer->width, buffer->height); buffer->width, buffer->height);
struct wlr_buffer_pass_options options = { 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); struct wlr_render_pass *pass = wlr_renderer_begin_buffer_pass(renderer, buffer, &options);
if (pass == NULL) { if (pass == NULL) {
@ -278,6 +278,7 @@ static struct wlr_buffer *render_cursor_buffer(struct wlr_output_cursor *cursor)
.transform = transform, .transform = transform,
.wait_timeline = cursor->wait_timeline, .wait_timeline = cursor->wait_timeline,
.wait_point = cursor->wait_point, .wait_point = cursor->wait_point,
.color_transform = cursor->color_transform_linearize,
}); });
if (!wlr_render_pass_submit(pass)) { 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); wlr_drm_syncobj_timeline_unref(cursor->wait_timeline);
wl_list_remove(&cursor->link); 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); free(cursor);
} }
bool output_cursor_refresh_color_transform(struct wlr_output_cursor *output_cursor, bool output_cursor_refresh_color_transform(struct wlr_output_cursor *output_cursor,
const struct wlr_output_image_description *img_desc) { const struct wlr_output_image_description *img_desc) {
wlr_color_transform_unref(output_cursor->color_transform); wlr_color_transform_unref(output_cursor->color_transform_linearize);
output_cursor->color_transform = NULL; 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) { if (img_desc == NULL) {
return true; 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; struct wlr_color_primaries primaries_srgb;
wlr_color_primaries_from_named(&primaries_srgb, WLR_COLOR_NAMED_PRIMARIES_SRGB); wlr_color_primaries_from_named(&primaries_srgb, WLR_COLOR_NAMED_PRIMARIES_SRGB);
struct wlr_color_primaries primaries; 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), wlr_color_transform_init_linear_to_inverse_eotf(img_desc->transfer_function),
}; };
if (transforms[0] == NULL || transforms[1] == NULL) { if (transforms[0] == NULL || transforms[1] == NULL) {
goto err;
}
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 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[0]);
wlr_color_transform_unref(transforms[1]); wlr_color_transform_unref(transforms[1]);
return false; return false;
}
output_cursor->color_transform = wlr_color_transform_init_pipeline(transforms,
sizeof(transforms) / sizeof(transforms[0]));
wlr_color_transform_unref(transforms[0]);
wlr_color_transform_unref(transforms[1]);
return output_cursor->color_transform != NULL;
} }

View file

@ -20,6 +20,7 @@
#include "types/wlr_scene.h" #include "types/wlr_scene.h"
#include "util/array.h" #include "util/array.h"
#include "util/env.h" #include "util/env.h"
#include "util/matrix.h"
#include "util/time.h" #include "util/time.h"
#include <wlr/config.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, static void scene_buffer_set_texture(struct wlr_scene_buffer *scene_buffer,
struct wlr_texture *texture); 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) { void wlr_scene_node_destroy(struct wlr_scene_node *node) {
if (node == NULL) { if (node == NULL) {
return; 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->linux_dmabuf_v1_destroy.link);
wl_list_remove(&scene->gamma_control_manager_v1_destroy.link); wl_list_remove(&scene->gamma_control_manager_v1_destroy.link);
wl_list_remove(&scene->gamma_control_manager_v1_set_gamma.link); wl_list_remove(&scene->gamma_control_manager_v1_set_gamma.link);
scene_color_transform_cache_destroy(scene->color_transform_cache);
} else { } else {
assert(node->parent); assert(node->parent);
} }
@ -165,6 +171,12 @@ struct wlr_scene *wlr_scene_create(void) {
return NULL; 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); scene_tree_init(&scene->tree, NULL);
wl_list_init(&scene->outputs); wl_list_init(&scene->outputs);
@ -319,6 +331,8 @@ struct render_data {
struct wlr_render_pass *render_pass; struct wlr_render_pass *render_pass;
pixman_region32_t damage; 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, 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); 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) { static void scene_entry_render(struct render_list_entry *entry, const struct render_data *data) {
struct wlr_scene_node *node = entry->node; 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){ wlr_render_pass_add_rect(data->render_pass, &(struct wlr_render_rect_options){
.box = dst_box, .box = dst_box,
.color = { .color = scene_color_transform_premultiplied(data->default_color_transform,
.r = scene_rect->color[0], scene_rect->color[0],
.g = scene_rect->color[1], scene_rect->color[1],
.b = scene_rect->color[2], scene_rect->color[2],
.a = scene_rect->color[3], scene_rect->color[3]),
},
.clip = &render_region, .clip = &render_region,
}); });
break; 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 // 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){ wlr_render_pass_add_rect(data->render_pass, &(struct wlr_render_rect_options){
.box = dst_box, .box = dst_box,
.color = { .color = scene_color_transform_premultiplied(data->default_color_transform,
.r = (float)scene_buffer->single_pixel_buffer_color[0] / (float)UINT32_MAX, (float)scene_buffer->single_pixel_buffer_color[0] / (float)UINT32_MAX,
.g = (float)scene_buffer->single_pixel_buffer_color[1] / (float)UINT32_MAX, (float)scene_buffer->single_pixel_buffer_color[1] / (float)UINT32_MAX,
.b = (float)scene_buffer->single_pixel_buffer_color[2] / (float)UINT32_MAX, (float)scene_buffer->single_pixel_buffer_color[2] / (float)UINT32_MAX,
.a = (float)scene_buffer->single_pixel_buffer_color[3] / (float)scene_buffer->single_pixel_buffer_color[3] /
(float)UINT32_MAX * scene_buffer->opacity, (float)UINT32_MAX * scene_buffer->opacity),
},
.clip = &render_region, .clip = &render_region,
}); });
break; 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); wlr_output_transform_invert(scene_buffer->transform);
transform = wlr_output_transform_compose(transform, data->transform); transform = wlr_output_transform_compose(transform, data->transform);
struct wlr_color_primaries primaries = {0}; struct wlr_color_transform *source_to_blend = NULL;
if (scene_buffer->primaries != 0) { if (data->output->output->renderer->features.input_color_transform) {
wlr_color_primaries_from_named(&primaries, scene_buffer->primaries); 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; source_to_blend = scene_texture_to_blend_space(scene_buffer, data->output,
wlr_color_transfer_function_get_default_luminance( WLR_COLOR_NAMED_PRIMARIES_SRGB, &srgb_lum);
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);
wlr_render_pass_add_texture(data->render_pass, &(struct wlr_render_texture_options) { wlr_render_pass_add_texture(data->render_pass, &(struct wlr_render_texture_options) {
.texture = texture, .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 || .blend_mode = !data->output->scene->calculate_visibility ||
!pixman_region32_empty(&opaque) ? !pixman_region32_empty(&opaque) ?
WLR_RENDER_BLEND_MODE_PREMULTIPLIED : WLR_RENDER_BLEND_MODE_NONE, WLR_RENDER_BLEND_MODE_PREMULTIPLIED : WLR_RENDER_BLEND_MODE_NONE,
.transfer_function = scene_buffer->transfer_function, .color_transform = source_to_blend,
.primaries = scene_buffer->primaries != 0 ? &primaries : NULL,
.color_encoding = scene_buffer->color_encoding, .color_encoding = scene_buffer->color_encoding,
.color_range = scene_buffer->color_range, .color_range = scene_buffer->color_range,
.luminance_multiplier = &luminance_multiplier,
.wait_timeline = scene_buffer->wait_timeline, .wait_timeline = scene_buffer->wait_timeline,
.wait_point = scene_buffer->wait_point, .wait_point = scene_buffer->wait_point,
}); });
wlr_color_transform_unref(source_to_blend);
struct wlr_scene_output_sample_event sample_event = { struct wlr_scene_output_sample_event sample_event = {
.output = data->output, .output = data->output,
.direct_scanout = false, .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) { if (entry->highlight_transparent_region) {
wlr_render_pass_add_rect(data->render_pass, &(struct wlr_render_rect_options){ wlr_render_pass_add_rect(data->render_pass, &(struct wlr_render_rect_options){
.box = dst_box, .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, .clip = &opaque,
}); });
} }
@ -2468,10 +2633,12 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output,
timer->pre_render_duration = timespec_to_nsec(&duration); timer->pre_render_duration = timespec_to_nsec(&duration);
} }
if (output->renderer->features.output_color_transform) {
if ((render_gamma_lut if ((render_gamma_lut
&& scene_output->gamma_lut_color_transform != scene_output->prev_gamma_lut_color_transform) && scene_output->gamma_lut_color_transform != scene_output->prev_gamma_lut_color_transform)
|| scene_output->prev_supplied_color_transform != options->color_transform || scene_output->prev_supplied_color_transform != options->color_transform
|| (state->committed & WLR_OUTPUT_STATE_IMAGE_DESCRIPTION)) { || (state->committed & WLR_OUTPUT_STATE_IMAGE_DESCRIPTION)
|| scene_output->combined_color_transform == NULL) {
const struct wlr_output_image_description *output_description = const struct wlr_output_image_description *output_description =
output_pending_image_description(output, state); output_pending_image_description(output, state);
if (!scene_output_combine_color_transforms(scene_output, options->color_transform, if (!scene_output_combine_color_transforms(scene_output, options->color_transform,
@ -2481,6 +2648,16 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output,
} }
} }
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++; scene_output->in_point++;
struct wlr_render_pass *render_pass = wlr_renderer_begin_buffer_pass(output->renderer, buffer, struct wlr_render_pass *render_pass = wlr_renderer_begin_buffer_pass(output->renderer, buffer,
&(struct wlr_buffer_pass_options){ &(struct wlr_buffer_pass_options){
@ -2491,6 +2668,7 @@ bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output,
}); });
if (render_pass == NULL) { if (render_pass == NULL) {
wlr_buffer_unlock(buffer); wlr_buffer_unlock(buffer);
wlr_color_transform_unref(render_data.default_color_transform);
return false; 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){ wlr_render_pass_add_rect(render_pass, &(struct wlr_render_rect_options){
.box = { .width = buffer->width, .height = buffer->height }, .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, .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)) { if (!wlr_render_pass_submit(render_pass)) {
wlr_buffer_unlock(buffer); 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 // if we failed to render the buffer, it will have undefined contents
// Trash the damage ring // 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_output_state_set_buffer(state, buffer);
wlr_buffer_unlock(buffer); wlr_buffer_unlock(buffer);
wlr_color_transform_unref(render_data.default_color_transform);
if (scene_output->in_timeline != NULL) { if (scene_output->in_timeline != NULL) {
wlr_output_state_set_wait_timeline(state, scene_output->in_timeline, wlr_output_state_set_wait_timeline(state, scene_output->in_timeline,