From 6fe0cfc2b836c5e2695a6d806d6c9beecd589a36 Mon Sep 17 00:00:00 2001 From: Leandro Ribeiro Date: Thu, 7 Aug 2025 17:04:14 -0300 Subject: [PATCH] color: introduce output color effects Output color effects are applied to the whole output scenegraph. It depends on color-management being disabled, as the color effects are applied in sRGB content. For now we added only a few accessibility options: color inversion, deuteranopia, protanopia and tritanopia CVD correction. Note that surfaces presented on outputs that contains a color effect can't be used for direct scanout (i.e. bypass composition and offloading to KMS overlay planes). The color effect is applied in our GL-renderer. Signed-off-by: Leandro Ribeiro --- frontend/main.c | 53 ++++++ include/libweston/libweston.h | 20 +++ include/libweston/linalg-3.h | 13 ++ libweston/backend-drm/state-propose.c | 7 +- libweston/color.h | 19 ++ libweston/compositor.c | 166 +++++++++++++++++- libweston/libweston-internal.h | 1 + libweston/renderer-gl/fragment.glsl | 64 +++++-- libweston/renderer-gl/gl-renderer-internal.h | 25 ++- libweston/renderer-gl/gl-renderer.c | 5 + .../gl-shader-config-color-transformation.c | 101 +++++++++++ libweston/renderer-gl/gl-shaders.c | 44 ++++- man/weston.ini.man | 22 +++ 13 files changed, 526 insertions(+), 14 deletions(-) diff --git a/frontend/main.c b/frontend/main.c index b7d457fdf..653273532 100644 --- a/frontend/main.c +++ b/frontend/main.c @@ -1385,6 +1385,47 @@ wet_output_set_vrr_mode(struct weston_output *output, return 0; } +static const struct weston_enum_map cvd_correction_name_map[] = { + { "deuteranopia", WESTON_CVD_CORRECTION_TYPE_DEUTERANOPIA }, + { "protanopia", WESTON_CVD_CORRECTION_TYPE_PROTANOPIA }, + { "tritanopia", WESTON_CVD_CORRECTION_TYPE_TRITANOPIA }, +}; + +static int +wet_output_set_color_effect(struct weston_output *output, + struct weston_config_section *section) +{ + struct wet_compositor *compositor = to_wet_compositor(output->compositor); + const struct weston_enum_map *entry; + char *color_effect = NULL; + bool ok = true; + + weston_config_section_get_string(section, "color-effect", &color_effect, NULL); + if (!color_effect) + return 0; + + if (compositor->use_color_manager) { + weston_log("Error: color effect can not be set for output %s, " \ + "color-management is enabled\n", output->name); + goto out; + } + + if (strcmp(color_effect, "inversion") == 0) { + weston_output_color_effect_inversion(output); + goto out; + } + + entry = weston_enum_map_find_name(cvd_correction_name_map, color_effect); + if (entry) + weston_output_color_effect_cvd_correction(output, entry->value); + else + weston_log("Error: unknown color effect '%s'\n", color_effect); + +out: + free(color_effect); + return ok ? 0 : -1; +} + static int wet_output_set_color_profile(struct weston_output *output, struct weston_config_section *section, @@ -1897,6 +1938,9 @@ wet_configure_windowed_output_from_config(struct weston_output *output, if (wet_output_set_color_profile(output, section, NULL) < 0) return -1; + if (wet_output_set_color_effect(output, section) < 0) + return -1; + if (api->output_set_size(output, width, height) < 0) { weston_log("Cannot configure output \"%s\" using weston_windowed_output_api.\n", output->name); @@ -2413,6 +2457,9 @@ drm_backend_output_configure(struct weston_output *output, if (wet_output_set_color_profile(output, section, NULL) < 0) return -1; + if (wet_output_set_color_effect(output, section) < 0) + return -1; + weston_config_section_get_string(section, "gbm-format", &gbm_format, NULL); @@ -3099,6 +3146,9 @@ drm_backend_remoted_output_configure(struct weston_output *output, if (wet_output_set_color_profile(output, section, NULL) < 0) return -1; + if (wet_output_set_color_effect(output, section) < 0) + return -1; + weston_config_section_get_string(section, "gbm-format", &gbm_format, NULL); api->set_gbm_format(output, gbm_format); @@ -3259,6 +3309,9 @@ drm_backend_pipewire_output_configure(struct weston_output *output, if (wet_output_set_color_profile(output, section, NULL) < 0) return -1; + if (wet_output_set_color_effect(output, section) < 0) + return -1; + weston_config_section_get_string(section, "seat", &seat, ""); api->set_seat(output, seat); diff --git a/include/libweston/libweston.h b/include/libweston/libweston.h index f897b9cb5..a6665d706 100644 --- a/include/libweston/libweston.h +++ b/include/libweston/libweston.h @@ -131,6 +131,17 @@ enum weston_surface_protection_mode { WESTON_SURFACE_PROTECTION_MODE_ENFORCED }; +enum weston_output_color_effect_type { + WESTON_OUTPUT_COLOR_EFFECT_TYPE_INVERSION = 0, + WESTON_OUTPUT_COLOR_EFFECT_TYPE_CVD_CORRECTION, +}; + +enum weston_cvd_correction_type { + WESTON_CVD_CORRECTION_TYPE_DEUTERANOPIA = 0, + WESTON_CVD_CORRECTION_TYPE_PROTANOPIA, + WESTON_CVD_CORRECTION_TYPE_TRITANOPIA, +}; + /** Possible mode of an output * * \ingroup output @@ -448,6 +459,8 @@ struct weston_output { struct weston_output_color_outcome *color_outcome; uint64_t color_outcome_serial; + struct weston_output_color_effect *color_effect; + int (*enable)(struct weston_output *output); int (*disable)(struct weston_output *output); @@ -2503,6 +2516,13 @@ void weston_output_set_transform(struct weston_output *output, uint32_t transform); +void +weston_output_color_effect_inversion(struct weston_output *output); + +void +weston_output_color_effect_cvd_correction(struct weston_output *output, + enum weston_cvd_correction_type type); + bool weston_output_set_color_profile(struct weston_output *output, struct weston_color_profile *cprof); diff --git a/include/libweston/linalg-3.h b/include/libweston/linalg-3.h index 2671f154d..2fe2ec698 100644 --- a/include/libweston/linalg-3.h +++ b/include/libweston/linalg-3.h @@ -175,6 +175,19 @@ weston_m3f_sub_m3f(struct weston_mat3f A, struct weston_mat3f B) return R; } +/** Element-wise scalar multiplication */ +static inline struct weston_mat3f +weston_m3f_mul_scalar(struct weston_mat3f M, float scalar) +{ + struct weston_mat3f R; + unsigned i; + + for (i = 0; i < 3 * 3; i++) + R.colmaj[i] = scalar * M.colmaj[i]; + + return R; +} + bool weston_m3f_invert(struct weston_mat3f *out, struct weston_mat3f M); diff --git a/libweston/backend-drm/state-propose.c b/libweston/backend-drm/state-propose.c index 68e7a44d0..b19b1a0a2 100644 --- a/libweston/backend-drm/state-propose.c +++ b/libweston/backend-drm/state-propose.c @@ -385,7 +385,8 @@ dmabuf_feedback_maybe_update(struct drm_device *device, struct weston_view *ev, FAILURE_REASONS_NO_GBM | FAILURE_REASONS_NO_COLOR_TRANSFORM | FAILURE_REASONS_SOLID_SURFACE | - FAILURE_REASONS_OCCLUDED_BY_RENDERER)) { + FAILURE_REASONS_OCCLUDED_BY_RENDERER | + FAILURE_REASONS_OUTPUT_COLOR_EFFECT)) { action_needed = ACTION_NEEDED_REMOVE_SCANOUT_TRANCHE; /* Direct scanout may be possible if client re-allocates using the * params from the scanout tranche. */ @@ -913,6 +914,10 @@ drm_output_propose_state(struct weston_output *output_base, pnode->try_view_on_plane_failure_reasons |= FAILURE_REASONS_SOLID_SURFACE; + if (pnode->output->color_effect) + pnode->try_view_on_plane_failure_reasons |= + FAILURE_REASONS_OUTPUT_COLOR_EFFECT; + if (pnode->surf_xform.transform != NULL || !pnode->surf_xform.identity_pipeline) pnode->try_view_on_plane_failure_reasons |= diff --git a/libweston/color.h b/libweston/color.h index 6c8be14e6..fcf523ea6 100644 --- a/libweston/color.h +++ b/libweston/color.h @@ -419,6 +419,25 @@ struct weston_color_transform { uint32_t len_lut3d, float *lut3d); }; +struct weston_cvd_correction { + enum weston_cvd_correction_type type; + struct weston_mat3f simulation; + struct weston_mat3f redistribution; +}; + +struct weston_output_color_effect { + struct weston_compositor *compositor; + struct wl_signal destroy_signal; + + /** Which member of 'u' defines the effect. */ + enum weston_output_color_effect_type type; + + union { + /* color inversion: no parameters */ + struct weston_cvd_correction cvd; + } u; +}; + /** * How content color needs to be transformed * diff --git a/libweston/compositor.c b/libweston/compositor.c index 8ee6546db..29c24f831 100644 --- a/libweston/compositor.c +++ b/libweston/compositor.c @@ -7210,6 +7210,165 @@ out_error: return false; } +static void +weston_output_color_effect_destroy(struct weston_output_color_effect *effect) +{ + if (!effect) + return; + + wl_signal_emit(&effect->destroy_signal, effect); + free(effect); +} + +static struct weston_output_color_effect * +weston_output_color_effect_create(struct weston_compositor *compositor) +{ + struct weston_output_color_effect *effect; + + effect = xzalloc(sizeof(*effect)); + effect->compositor = compositor; + wl_signal_init(&effect->destroy_signal); + + return effect; +} + +/** Set output's color effect as color inversion + * + * The color effect is an effect applied to the whole scenegraph. Note that in + * some cases this may force all the surfaces to be composed on the primary + * plane, i.e. offloading to overlay planes won't be possible. + * + * \param output The output to set the effect. + * + * \ingroup output + */ +WL_EXPORT void +weston_output_color_effect_inversion(struct weston_output *output) +{ + struct weston_compositor *compositor = output->compositor; + + weston_assert_ptr_null(compositor, output->color_effect); + + output->color_effect = weston_output_color_effect_create(compositor); + output->color_effect->type = WESTON_OUTPUT_COLOR_EFFECT_TYPE_INVERSION; +} + +/** Set output's color effect as CVD correction + * + * The color effect is an effect applied to the whole scenegraph. Note that in + * some cases this may force all the surfaces to be composed on the primary + * plane, i.e. offloading to overlay planes won't be possible. + * + * \param output The output to set the effect. + * \param type The color blidness correction type. + * + * \ingroup output + */ +WL_EXPORT void +weston_output_color_effect_cvd_correction(struct weston_output *output, + enum weston_cvd_correction_type type) +{ + struct weston_compositor *compositor = output->compositor; + struct weston_cvd_correction *cvd; + float correction_factor = 0.7f; /* TODO: allow tweaking this from ini */ + + /** + * Color vision correction algorithms depend on color vision deficiency + * (CVD) simulation. The majority of FOSS applications uses CVD + * simulation and/or correction pipelines that expects content in sRGB + * color space. + * + * To get an idea of how CVD simulation works, "Designing for Color + * blindness" [1] is a good source. "Online Color Blindness Simulators" + * [2] is another good source, as it contains comparison between + * pipelines for simulating CVD, and you can upload images and check the + * results of each model. They also created Daltonlens-Python [3], which + * provides implementation of all famous methods. + * + * The most reliable method to perform CVD simulation was developed in + * Brettel et al. "Computerized simulation of color appearance for + * dichromats" [4]. But it is expensive for us to apply that for every + * frame, so we need something else. + * + * Many modern applications (as Firefox, see [5]) are using the CVD + * simulation matrices from Machado et al. "A Physiologically-based + * Model for Simulation of Color Vision Deficiency" [6], both because + * baked-in matrices pipelines are easy to implement and efficient, but + * also because they seem to produce nice results. Such matrices are + * usually applied in linear RGB, althought there are discussions [7] + * mentioning that authors didn't notice differences applying their + * simulation matrices directly to electrical. So we do that in + * electrical. + * + * After simulating the CVD, we need to apply the correction. We compute + * the "error" (original linear RGB minus simulation), and use the + * redistribution matrix to shift the lost content to the other color + * channels (depending on the CVD type). This shifted content is summed + * to the original, but we multiply it by a correction_factor first: + * + * color = original + correction_factor * (redistribution_matrix * error) + * + * We pre-multiply the redistribution matrix by this correction_factor + * here just to avoid carrying the coefficient. + * + * [1] https://mk.bcgsc.ca/colorblind/math.mhtml#projecthome + * [2] https://daltonlens.org/colorblindness-simulator + * [3] https://github.com/DaltonLens/DaltonLens-Python + * [4] https://vision.psychol.cam.ac.uk/jdmollon/papers/Dichromatsimulation.pdf + * [5] https://firefox-source-docs.mozilla.org/devtools-user/accessibility_inspector/simulation/index.html + * [6] https://www.inf.ufrgs.br/~oliveira/pubs_files/CVD_Simulation/CVD_Simulation.html + * [7] https://bugzilla.mozilla.org/show_bug.cgi?id=1655053#c22 + */ + + weston_assert_ptr_null(compositor, output->color_effect); + + output->color_effect = weston_output_color_effect_create(compositor); + output->color_effect->type = WESTON_OUTPUT_COLOR_EFFECT_TYPE_CVD_CORRECTION; + + cvd = &output->color_effect->u.cvd; + cvd->type = type; + + /** + * CVD simulation matrices from "A Physiologically-based Model for + * Simulation of Color Deficiency". The redistribution matrices are + * based on each type of CVD (i.e. the defective cone). We use the + * 100% severity matrices for the simulation. + */ + 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; + 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; + 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; + } + weston_assert_not_reached(compositor, "unknown color correction type"); +} + /** Removes output from compositor's list of enabled outputs * * \param output The weston_output object that is being removed. @@ -8041,6 +8200,7 @@ weston_output_get_destroy_listener(struct weston_output *output, WL_EXPORT void weston_output_release(struct weston_output *output) { + struct weston_compositor *compositor = output->compositor; struct weston_head *head, *tmp; output->destroying = 1; @@ -8050,13 +8210,16 @@ weston_output_release(struct weston_output *output) if (output->enabled) weston_compositor_remove_output(output); + weston_assert_ptr_null(compositor, output->color_outcome); + /* We always have a color profile set, as weston_output_init() sets the * output cprof to the stock sRGB one. */ assert(output->color_profile); weston_color_profile_unref(output->color_profile); output->color_profile = NULL; - assert(output->color_outcome == NULL); + weston_output_color_effect_destroy(output->color_effect); + output->color_effect = NULL; pixman_region32_fini(&output->region); wl_list_remove(&output->link); @@ -8958,6 +9121,7 @@ weston_plane_failure_reasons_to_str(enum try_view_on_plane_failure_reasons failu case FAILURE_REASONS_NO_COLOR_TRANSFORM: return "no color transform"; case FAILURE_REASONS_SOLID_SURFACE: return "solid surface"; case FAILURE_REASONS_OCCLUDED_BY_RENDERER: return "occluded by renderer"; + case FAILURE_REASONS_OUTPUT_COLOR_EFFECT: return "output contains color effect"; } return "???"; } diff --git a/libweston/libweston-internal.h b/libweston/libweston-internal.h index bea3a56b6..799248ac0 100644 --- a/libweston/libweston-internal.h +++ b/libweston/libweston-internal.h @@ -675,6 +675,7 @@ enum try_view_on_plane_failure_reasons { FAILURE_REASONS_NO_COLOR_TRANSFORM = 1 << 15, FAILURE_REASONS_SOLID_SURFACE = 1 << 16, FAILURE_REASONS_OCCLUDED_BY_RENDERER = 1 << 17, + FAILURE_REASONS_OUTPUT_COLOR_EFFECT = 1 << 18, }; /** diff --git a/libweston/renderer-gl/fragment.glsl b/libweston/renderer-gl/fragment.glsl index 6a4d3d82e..e02329d75 100644 --- a/libweston/renderer-gl/fragment.glsl +++ b/libweston/renderer-gl/fragment.glsl @@ -41,6 +41,11 @@ #define SHADER_VARIANT_SOLID 5 #define SHADER_VARIANT_EXTERNAL 6 +/* enum gl_shader_color_effect */ +#define SHADER_COLOR_EFFECT_NONE 0 +#define SHADER_COLOR_EFFECT_INVERSION 1 +#define SHADER_COLOR_EFFECT_CVD_CORRECTION 2 + /* enum gl_shader_color_curve */ #define SHADER_COLOR_CURVE_IDENTITY 0 #define SHADER_COLOR_CURVE_LUT_3x1D 1 @@ -78,6 +83,7 @@ compile_const int c_variant = DEF_VARIANT; compile_const int c_color_pre_curve = DEF_COLOR_PRE_CURVE; compile_const int c_color_mapping = DEF_COLOR_MAPPING; compile_const int c_color_post_curve = DEF_COLOR_POST_CURVE; +compile_const int c_color_effect = DEF_COLOR_EFFECT; compile_const bool c_input_is_premult = DEF_INPUT_IS_PREMULT; compile_const bool c_tint = DEF_TINT; @@ -86,6 +92,8 @@ compile_const bool c_need_color_pipeline = c_color_pre_curve != SHADER_COLOR_CURVE_IDENTITY || c_color_mapping != SHADER_COLOR_MAPPING_IDENTITY || c_color_post_curve != SHADER_COLOR_CURVE_IDENTITY; +compile_const bool c_need_straight_alpha = + c_need_color_pipeline || c_color_effect != SHADER_COLOR_EFFECT_NONE; vec4 yuva2rgba(vec4 yuva) @@ -161,6 +169,9 @@ 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; + /* * 2D texture sampler abstracting away the lack of swizzles on OpenGL ES 2. This * should only be used by code relying on swizzling. 'unit' is the texture unit @@ -459,14 +470,6 @@ color_mapping(vec3 color) vec4 color_pipeline(vec4 color) { - /* Ensure straight alpha */ - if (c_input_is_premult) { - if (color.a == 0.0) - color.rgb = vec3(0, 0, 0); - else - color.rgb *= 1.0 / color.a; - } - color.rgb = color_curve(c_color_pre_curve, color_pre_curve_lut, color_pre_curve_par, color.rgb); color.rgb = color_mapping(color.rgb); @@ -476,6 +479,34 @@ color_pipeline(vec4 color) return color; } +vec4 +color_inversion(vec4 color) +{ + color.rgb = 1.0 - color.rgb; + + return color; +} + +vec4 +color_cvd_correction(vec4 color) +{ + vec3 original, error; + vec4 res; + + /** + * See weston_output_color_effect_cvd_correction() for more details. + */ + + original = color.rgb; + + color.rgb = color_cvd_simulation * original; + error = original - color.rgb; + color.rgb = original + color_cvd_redistribution * error; + color.rgb = clamp(color.rgb, 0.0, 1.0); + + return color; +} + vec4 wireframe() { @@ -494,11 +525,24 @@ main() /* Electrical (non-linear) RGBA values, may be premult or not */ color = sample_input_texture(); + /* Ensure straight alpha for color pipeline and color effects */ + if (c_input_is_premult && c_need_straight_alpha) { + if (color.a == 0.0) + color.rgb = vec3(0, 0, 0); + else + color.rgb *= 1.0 / color.a; + } + + /* For now color management and color effects do not coexist */ if (c_need_color_pipeline) - color = color_pipeline(color); /* Produces straight alpha */ + color = color_pipeline(color); + else if (c_color_effect == SHADER_COLOR_EFFECT_INVERSION) + color = color_inversion(color); + else if (c_color_effect == SHADER_COLOR_EFFECT_CVD_CORRECTION) + color = color_cvd_correction(color); /* Ensure pre-multiplied for blending */ - if (!c_input_is_premult || c_need_color_pipeline) + if (!c_input_is_premult || (c_input_is_premult && c_need_straight_alpha)) color.rgb *= color.a; color *= view_alpha; diff --git a/libweston/renderer-gl/gl-renderer-internal.h b/libweston/renderer-gl/gl-renderer-internal.h index 55ce937e7..65d527619 100644 --- a/libweston/renderer-gl/gl-renderer-internal.h +++ b/libweston/renderer-gl/gl-renderer-internal.h @@ -222,6 +222,13 @@ enum gl_shader_texture_variant { SHADER_VARIANT_EXTERNAL, }; +/* Keep the following in sync with fragment.glsl. */ +enum gl_shader_color_effect { + SHADER_COLOR_EFFECT_NONE = 0, + SHADER_COLOR_EFFECT_INVERSION, + SHADER_COLOR_EFFECT_CVD_CORRECTION, +}; + /* Keep the following in sync with fragment.glsl. */ enum gl_shader_color_curve { SHADER_COLOR_CURVE_IDENTITY = 0, @@ -289,6 +296,8 @@ struct gl_shader_requirements bool tint:1; bool wireframe:1; + unsigned color_effect:2; /* enum gl_shader_color_effect */ + unsigned color_pre_curve:3; /* enum gl_shader_color_curve */ unsigned color_mapping:2; /* enum gl_shader_color_mapping */ unsigned color_post_curve:3; /* enum gl_shader_color_curve */ @@ -297,7 +306,7 @@ struct gl_shader_requirements * The total size of all bitfields plus pad_bits_ must fill up exactly * how many bytes the compiler allocates for them together. */ - unsigned pad_bits_:16; + unsigned pad_bits_:14; }; static_assert(sizeof(struct gl_shader_requirements) == 4 /* total bitfield size in bytes */, @@ -333,6 +342,13 @@ struct gl_shader; struct weston_color_transform; struct dmabuf_allocator; +union gl_shader_config_color_effect { + struct { + struct weston_mat3f simulation; + struct weston_mat3f redistribution; + } cvd_correction; +}; + union gl_shader_config_color_curve { struct { GLuint tex; @@ -369,6 +385,8 @@ struct gl_shader_config { GLuint wireframe_tex; + union gl_shader_config_color_effect color_effect; + union gl_shader_config_color_curve color_pre_curve; union gl_shader_config_color_mapping color_mapping; union gl_shader_config_color_curve color_post_curve; @@ -734,4 +752,9 @@ gl_shader_config_set_color_transform(struct gl_renderer *gr, struct gl_shader_config *sconf, struct weston_color_transform *xform); +bool +gl_shader_config_set_color_effect(struct gl_renderer *gr, + struct gl_shader_config *sconf, + struct weston_output_color_effect *effect); + #endif /* GL_RENDERER_INTERNAL_H */ diff --git a/libweston/renderer-gl/gl-renderer.c b/libweston/renderer-gl/gl-renderer.c index 6e1029c85..99126fdf4 100644 --- a/libweston/renderer-gl/gl-renderer.c +++ b/libweston/renderer-gl/gl-renderer.c @@ -1503,6 +1503,11 @@ gl_shader_config_init_for_paint_node(struct gl_shader_config *sconf, return false; } + if (!gl_shader_config_set_color_effect(gr, sconf, pnode->output->color_effect)) { + weston_log("GL-renderer: %s failed to generate a color effect.\n", __func__); + return false; + } + return true; } diff --git a/libweston/renderer-gl/gl-shader-config-color-transformation.c b/libweston/renderer-gl/gl-shader-config-color-transformation.c index 33186e870..2dd7a13f9 100644 --- a/libweston/renderer-gl/gl-shader-config-color-transformation.c +++ b/libweston/renderer-gl/gl-shader-config-color-transformation.c @@ -56,6 +56,13 @@ struct gl_renderer_color_transform { struct gl_renderer_color_curve post_curve; }; +struct gl_renderer_color_effect { + struct weston_output_color_effect *owner; + struct wl_listener destroy_listener; + enum gl_shader_color_effect type; + union gl_shader_config_color_effect u; +}; + static void gl_renderer_color_curve_fini(struct gl_renderer_color_curve *gl_curve) { @@ -466,3 +473,97 @@ gl_shader_config_set_color_transform(struct gl_renderer *gr, return true; } + +static void +gl_renderer_color_effect_destroy(struct gl_renderer_color_effect *gl_effect) +{ + wl_list_remove(&gl_effect->destroy_listener.link); + free(gl_effect); +} + +static void +color_effect_destroy_handler(struct wl_listener *l, void *data) +{ + struct gl_renderer_color_effect *gl_effect; + + gl_effect = wl_container_of(l, gl_effect, destroy_listener); + weston_assert_ptr_eq(gl_effect->owner->compositor, gl_effect->owner, data); + + gl_renderer_color_effect_destroy(gl_effect); +} + +static struct gl_renderer_color_effect * +gl_renderer_color_effect_create(struct weston_output_color_effect *effect) +{ + struct gl_renderer_color_effect *gl_effect; + + gl_effect = zalloc(sizeof *gl_effect); + if (!gl_effect) + return NULL; + + gl_effect->owner = effect; + gl_effect->destroy_listener.notify = color_effect_destroy_handler; + wl_signal_add(&effect->destroy_signal, &gl_effect->destroy_listener); + + return gl_effect; +} + +static struct gl_renderer_color_effect * +gl_renderer_color_effect_get(struct weston_output_color_effect *effect) +{ + struct wl_listener *l; + + l = wl_signal_get(&effect->destroy_signal, + color_effect_destroy_handler); + if (!l) + return NULL; + + return container_of(l, struct gl_renderer_color_effect, + destroy_listener); +} + +static const struct gl_renderer_color_effect * +gl_renderer_color_effect_from(struct weston_output_color_effect *effect) +{ + struct gl_renderer_color_effect *gl_effect; + + /* Cached effect */ + gl_effect = gl_renderer_color_effect_get(effect); + if (gl_effect) + return gl_effect; + + /* New effect */ + return gl_renderer_color_effect_create(effect); +} + +bool +gl_shader_config_set_color_effect(struct gl_renderer *gr, + struct gl_shader_config *sconf, + struct weston_output_color_effect *effect) +{ + const struct gl_renderer_color_effect *gl_effect; + + if (!effect) { + sconf->req.color_effect = SHADER_COLOR_EFFECT_NONE; + return true; + } + + gl_effect = gl_renderer_color_effect_from(effect); + if (!gl_effect) + return false; + + switch (effect->type) { + case WESTON_OUTPUT_COLOR_EFFECT_TYPE_INVERSION: + sconf->req.color_effect = SHADER_COLOR_EFFECT_INVERSION; + 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; + break; + } + weston_assert_u32_ne(gr->compositor, sconf->req.color_effect, + SHADER_COLOR_EFFECT_NONE); + + return true; +} diff --git a/libweston/renderer-gl/gl-shaders.c b/libweston/renderer-gl/gl-shaders.c index 9477e97b6..40c2995d4 100644 --- a/libweston/renderer-gl/gl-shaders.c +++ b/libweston/renderer-gl/gl-shaders.c @@ -52,6 +52,11 @@ /* 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; @@ -90,6 +95,7 @@ struct gl_shader { GLint view_alpha_uniform; GLint color_uniform; GLint tint_uniform; + struct gl_shader_cvd_correction_uniforms cvd; 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; @@ -126,6 +132,20 @@ gl_shader_texture_variant_to_string(enum gl_shader_texture_variant v) return "!?!?"; /* never reached */ } +static const char * +gl_shader_color_effect_to_string(enum gl_shader_color_effect kind) +{ + switch(kind) { +#define CASERET(x) case x: return #x; + CASERET(SHADER_COLOR_EFFECT_NONE) + CASERET(SHADER_COLOR_EFFECT_INVERSION) + CASERET(SHADER_COLOR_EFFECT_CVD_CORRECTION) +#undef CASERET + } + + return "!?!?"; /* never reached */ +} + static const char * gl_shader_color_curve_to_string(enum gl_shader_color_curve kind) { @@ -222,9 +242,10 @@ create_shader_description_string(const struct gl_shader_requirements *req) int size; char *str; - size = asprintf(&str, "%s %s %s %s %s %cinput_is_premult %ctint", + size = asprintf(&str, "%s %s %s %s %s %s %cinput_is_premult %ctint", gl_shader_texcoord_input_to_string(req->texcoord_input), gl_shader_texture_variant_to_string(req->variant), + gl_shader_color_effect_to_string(req->color_effect), gl_shader_color_curve_to_string(req->color_pre_curve), gl_shader_color_mapping_to_string(req->color_mapping), gl_shader_color_curve_to_string(req->color_post_curve), @@ -266,6 +287,7 @@ create_fragment_shader_config_string(const struct gl_shader_requirements *req) "#define DEF_COLOR_PRE_CURVE %s\n" "#define DEF_COLOR_MAPPING %s\n" "#define DEF_COLOR_POST_CURVE %s\n" + "#define DEF_COLOR_EFFECT %s\n" "#define DEF_VARIANT %s\n", ARRAY_LENGTH(((union weston_color_curve_parametric_chan_data){}).data), req->tint ? "true" : "false", @@ -274,6 +296,7 @@ create_fragment_shader_config_string(const struct gl_shader_requirements *req) gl_shader_color_curve_to_string(req->color_pre_curve), gl_shader_color_mapping_to_string(req->color_mapping), gl_shader_color_curve_to_string(req->color_post_curve), + gl_shader_color_effect_to_string(req->color_effect), gl_shader_texture_variant_to_string(req->variant)); if (size < 0) return NULL; @@ -443,6 +466,16 @@ gl_shader_create(struct gl_renderer *gr, shader->tint_uniform = -1; } + 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"); + } else { + shader->cvd.simulation_uniform = -1; + shader->cvd.redistribution_uniform = -1; + } + get_curve_uniform_locations(gr, &shader->color_pre_curve, requirements->color_pre_curve, shader->program, "color_pre_curve"); @@ -807,6 +840,15 @@ 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, + 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); + /* Fixed texture unit for color_pre_curve LUT if it is available */ gl_shader_load_config_curve(gr->compositor, sconf->req.color_pre_curve, &sconf->color_pre_curve, &shader->color_pre_curve, diff --git a/man/weston.ini.man b/man/weston.ini.man index da795fba4..a4cdfe0d0 100644 --- a/man/weston.ini.man +++ b/man/weston.ini.man @@ -586,6 +586,28 @@ is true, load the given ICC file as the output color profile. This works only on DRM, headless, wayland, and x11 backends, and for remoting and pipewire outputs. .TP 7 +.BI "color-effect=" name +Use the given color effect for the output. For now we only provide color effects +for color vision deficiencies (CVD). Only works when option +.B color-management +is false. + +The effect can be one of the following strings: +.RS 11 +.TP +.B inversion +color inversion +.TP +.B deuteranopia +color correction (not simulation) for deuteranopia +.TP +.B protanopia +color correction (not simulation) for protanopia +.TP +.B tritanopia +color correction (not simulation) for tritanopia +.RE +.TP 7 .BI "seat=" name The logical seat name that this output should be associated with. If this is set then the seat's input will be confined to the output that has the seat