libweston: collapse CVD correction into single matrix multiplication

CVD correction is composed by a linear transformation, so we should be
able to collapse it into a single matrix multiplication:

Y = M * X

Where Y is the result, X is the original color, and M is the CVD
correction matrix.

As we need to perform CVD correction for every pixel, this can be
beneficial for limited hardware.

In this patch we do that, updating the libweston core code and also the
GL-renderer and its fragment shader.

Suggested-by: Christopher Healy <healych@amazon.com>
Signed-off-by: Leandro Ribeiro <leandro.ribeiro@collabora.com>
This commit is contained in:
Leandro Ribeiro 2026-01-12 19:30:47 -03:00 committed by Marius Vlad
parent 10991a4c4a
commit 3de7c52942
8 changed files with 73 additions and 67 deletions

View file

@ -212,6 +212,19 @@ weston_m3f_mul_m3f(struct weston_mat3f A, struct weston_mat3f B)
return result;
}
/** Element-wise matrix addition A + B */
static inline struct weston_mat3f
weston_m3f_add_m3f(struct weston_mat3f A, struct weston_mat3f B)
{
struct weston_mat3f R;
unsigned i;
for (i = 0; i < 3 * 3; i++)
R.colmaj[i] = A.colmaj[i] + B.colmaj[i];
return R;
}
/** Element-wise matrix subtraction A - B */
static inline struct weston_mat3f
weston_m3f_sub_m3f(struct weston_mat3f A, struct weston_mat3f B)

View file

@ -424,8 +424,7 @@ struct weston_color_transform {
struct weston_cvd_correction {
enum weston_cvd_correction_type type;
struct weston_mat3f simulation;
struct weston_mat3f redistribution;
struct weston_mat3f correction;
};
struct weston_output_color_effect {

View file

@ -7643,6 +7643,7 @@ weston_output_color_effect_cvd_correction(struct weston_output *output,
{
struct weston_compositor *compositor = output->compositor;
struct weston_cvd_correction *cvd;
struct weston_mat3f simul, redist;
float correction_factor = 0.7f; /* TODO: allow tweaking this from ini */
/**
@ -7681,8 +7682,22 @@ weston_output_color_effect_cvd_correction(struct weston_output *output,
*
* color = original + correction_factor * (redistribution_matrix * error)
*
* We pre-multiply the redistribution matrix by this correction_factor
* here just to avoid carrying the coefficient.
* We collapse everything into a single matrix to avoid multiple matrix
* operations per pixel in the renderers:
*
* Let:
* R = correction_factor * redistribution_matrix
* S = simulation_matrix
* X = original
* I = identity
*
* Note that we multiply redistribution_matrix by correction_factor
* to simplify the equations.
*
* Y = X + RE
* Y = X + R * (X - SX)
* Y = X + RX - RSX
* Y = (I + R - RS) * X
*
* [1] https://mk.bcgsc.ca/colorblind/math.mhtml#projecthome
* [2] https://daltonlens.org/colorblindness-simulator
@ -7709,37 +7724,38 @@ weston_output_color_effect_cvd_correction(struct weston_output *output,
*/
switch (cvd->type) {
case WESTON_CVD_CORRECTION_TYPE_DEUTERANOPIA:
cvd->simulation = WESTON_MAT3F( 0.367322, 0.860646, -0.227968,
0.280085, 0.672501, 0.047413,
-0.011820, 0.042940, 0.968881);
cvd->redistribution = WESTON_MAT3F(1.0, 0.5, 0.0, /* redistribute green */
0.0, 0.0, 0.0,
0.0, 0.5, 1.0);
cvd->redistribution = weston_m3f_mul_scalar(cvd->redistribution,
correction_factor);
return;
simul = WESTON_MAT3F( 0.367322, 0.860646, -0.227968,
0.280085, 0.672501, 0.047413,
-0.011820, 0.042940, 0.968881);
redist = WESTON_MAT3F(1.0, 0.5, 0.0, /* redistribute green */
0.0, 0.0, 0.0,
0.0, 0.5, 1.0);
goto out;
case WESTON_CVD_CORRECTION_TYPE_PROTANOPIA:
cvd->simulation = WESTON_MAT3F( 0.152286, 1.052583, -0.204868,
0.114503, 0.786281, 0.099216,
-0.003882, -0.048116, 1.051998);
cvd->redistribution = WESTON_MAT3F(0.0, 0.0, 0.0, /* redistribute red */
0.5, 1.0, 0.0,
0.5, 0.0, 1.0);
cvd->redistribution = weston_m3f_mul_scalar(cvd->redistribution,
correction_factor);
return;
simul = WESTON_MAT3F( 0.152286, 1.052583, -0.204868,
0.114503, 0.786281, 0.099216,
-0.003882, -0.048116, 1.051998);
redist = WESTON_MAT3F(0.0, 0.0, 0.0, /* redistribute red */
0.5, 1.0, 0.0,
0.5, 0.0, 1.0);
goto out;
case WESTON_CVD_CORRECTION_TYPE_TRITANOPIA:
cvd->simulation = WESTON_MAT3F( 1.255528, -0.076749, -0.178779,
-0.078411, 0.930809, 0.147602,
0.004733, 0.691367, 0.303900);
cvd->redistribution = WESTON_MAT3F(1.0, 0.0, 0.5, /* redistribute blue */
0.0, 1.0, 0.5,
0.0, 0.0, 0.0);
cvd->redistribution = weston_m3f_mul_scalar(cvd->redistribution,
correction_factor);
return;
simul = WESTON_MAT3F( 1.255528, -0.076749, -0.178779,
-0.078411, 0.930809, 0.147602,
0.004733, 0.691367, 0.303900);
redist = WESTON_MAT3F(1.0, 0.0, 0.5, /* redistribute blue */
0.0, 1.0, 0.5,
0.0, 0.0, 0.0);
goto out;
}
weston_assert_not_reached(compositor, "unknown color correction type");
out:
/* Y = (I + RS - R) * X */
redist = weston_m3f_mul_scalar(redist, correction_factor);
cvd->correction = weston_m3f_add_m3f(WESTON_MAT3F_IDENTITY, redist);
cvd->correction = weston_m3f_sub_m3f(cvd->correction,
weston_m3f_mul_m3f(redist, simul));
}
/** Removes output from compositor's list of enabled outputs

View file

@ -157,8 +157,7 @@ uniform HIGHPRECISION vec2 color_mapping_lut_scale_offset;
uniform HIGHPRECISION mat3 color_mapping_matrix;
uniform HIGHPRECISION vec3 color_mapping_offset;
uniform HIGHPRECISION mat3 color_cvd_simulation;
uniform HIGHPRECISION mat3 color_cvd_redistribution;
uniform HIGHPRECISION mat3 cvd_correction_matrix;
/*
* 2D texture sampler abstracting away the lack of swizzles on OpenGL ES 2. This
@ -460,8 +459,7 @@ color_inversion(vec4 color)
vec4
color_cvd_correction(vec4 color)
{
vec3 original, error;
vec4 res;
vec3 original;
/**
* See weston_output_color_effect_cvd_correction() for more details.
@ -469,9 +467,7 @@ color_cvd_correction(vec4 color)
original = color.rgb;
color.rgb = color_cvd_simulation * original;
error = original - color.rgb;
color.rgb = original + color_cvd_redistribution * error;
color.rgb = cvd_correction_matrix * original;
color.rgb = clamp(color.rgb, 0.0, 1.0);
return color;

View file

@ -343,10 +343,7 @@ struct weston_color_transform;
struct dmabuf_allocator;
union gl_shader_config_color_effect {
struct {
struct weston_mat3f simulation;
struct weston_mat3f redistribution;
} cvd_correction;
struct weston_mat3f cvd_correction;
};
union gl_shader_config_color_curve {

View file

@ -2201,7 +2201,7 @@ apply_color_effect(struct weston_output *output, float *r, float *g, float *b, c
struct weston_compositor *compositor = output->compositor;
struct weston_output_color_effect *effect = output->color_effect;
struct weston_vec3f input = WESTON_VEC3F(*r, *g, *b);
struct weston_vec3f res, err;
struct weston_vec3f res;
/*
* Caller guarantees alpha is always 0.0f (fully transparent) or 1.0f
@ -2224,9 +2224,7 @@ apply_color_effect(struct weston_output *output, float *r, float *g, float *b, c
/**
* See weston_output_color_effect_cvd_correction() for more details.
*/
res = weston_m3f_mul_v3f(effect->u.cvd.simulation, input);
err = weston_v3f_sub_v3f(input, res);
res = weston_v3f_add_v3f(input, weston_m3f_mul_v3f(effect->u.cvd.redistribution, err));
res = weston_m3f_mul_v3f(effect->u.cvd.correction, input);
res = weston_v3f_clamp(res, 0.0f, 1.0f);
*r = res.el[0];
*g = res.el[1];

View file

@ -565,8 +565,7 @@ gl_shader_config_set_color_effect(struct gl_renderer *gr,
break;
case WESTON_OUTPUT_COLOR_EFFECT_TYPE_CVD_CORRECTION:
sconf->req.color_effect = SHADER_COLOR_EFFECT_CVD_CORRECTION;
sconf->color_effect.cvd_correction.simulation = effect->u.cvd.simulation;
sconf->color_effect.cvd_correction.redistribution = effect->u.cvd.redistribution;
sconf->color_effect.cvd_correction = effect->u.cvd.correction;
break;
}
weston_assert_u32_ne(gr->compositor, sconf->req.color_effect,

View file

@ -53,11 +53,6 @@
/* static const char fragment_shader[]; fragment.glsl */
#include "fragment-shader.h"
struct gl_shader_cvd_correction_uniforms {
GLint simulation_uniform;
GLint redistribution_uniform;
};
union gl_shader_color_curve_uniforms {
struct {
GLint tex_2d_uniform;
@ -96,7 +91,7 @@ struct gl_shader {
GLint view_alpha_uniform;
GLint color_uniform;
GLint tint_uniform;
struct gl_shader_cvd_correction_uniforms cvd;
GLint cvd_correction_uniform;
union gl_shader_color_curve_uniforms color_pre_curve;
union gl_shader_color_mapping_uniforms color_mapping;
union gl_shader_color_curve_uniforms color_post_curve;
@ -470,13 +465,10 @@ gl_shader_create(struct gl_renderer *gr,
}
if (requirements->color_effect == SHADER_COLOR_EFFECT_CVD_CORRECTION) {
shader->cvd.simulation_uniform =
glGetUniformLocation(shader->program, "color_cvd_simulation");
shader->cvd.redistribution_uniform =
glGetUniformLocation(shader->program, "color_cvd_redistribution");
shader->cvd_correction_uniform =
glGetUniformLocation(shader->program, "cvd_correction_matrix");
} else {
shader->cvd.simulation_uniform = -1;
shader->cvd.redistribution_uniform = -1;
shader->cvd_correction_uniform = -1;
}
get_curve_uniform_locations(gr, &shader->color_pre_curve,
@ -871,14 +863,10 @@ gl_shader_load_config(struct gl_renderer *gr,
gl_texture_parameters_flush(gr, &sconf->input_param[i]);
}
if (shader->cvd.simulation_uniform)
glUniformMatrix3fv(shader->cvd.simulation_uniform,
if (shader->cvd_correction_uniform)
glUniformMatrix3fv(shader->cvd_correction_uniform,
1, GL_FALSE,
sconf->color_effect.cvd_correction.simulation.colmaj);
if (shader->cvd.redistribution_uniform)
glUniformMatrix3fv(shader->cvd.redistribution_uniform,
1, GL_FALSE,
sconf->color_effect.cvd_correction.redistribution.colmaj);
sconf->color_effect.cvd_correction.colmaj);
/* Fixed texture unit for color_pre_curve LUT if it is available */
gl_shader_load_config_curve(gr->compositor, sconf->req.color_pre_curve,