diff --git a/libweston/color-operations.c b/libweston/color-operations.c index b7f5c3af6..6f0158846 100644 --- a/libweston/color-operations.c +++ b/libweston/color-operations.c @@ -1,5 +1,5 @@ /* - * Copyright 2025 Collabora, Ltd. + * Copyright 2025-2026 Collabora, Ltd. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -31,6 +31,7 @@ #include "shared/helpers.h" #include "shared/weston-assert.h" +#include "shared/xalloc.h" static float linpow(float x, const union weston_color_curve_parametric_chan_data *p) @@ -234,3 +235,211 @@ weston_color_curve_sample(struct weston_compositor *compositor, weston_assert_not_reached(compositor, "unknown color curve"); } + +/** Reduce a color transform into a curve + * + * @param xform The color transform to inspect. + * @return A pointer to a curve step in the given color transform, + * or NULL if the whole color transform cannot be reduced into one curve step. + */ +WL_EXPORT const struct weston_color_curve * +weston_color_transform_as_single_curve(const struct weston_color_transform *xform) +{ + const struct weston_color_curve *curve = NULL; + + if (!xform->steps_valid) + return NULL; + + if (xform->pre_curve.type != WESTON_COLOR_CURVE_TYPE_IDENTITY) + curve = &xform->pre_curve; + + if (xform->mapping.type != WESTON_COLOR_MAPPING_TYPE_IDENTITY) + return NULL; + + if (curve) { + if (xform->post_curve.type != WESTON_COLOR_CURVE_TYPE_IDENTITY) + return NULL; + } else { + curve = &xform->post_curve; + } + + return curve; +} + +static bool +invert_linpow(union weston_color_curve_parametric_chan_data *inv, + const union weston_color_curve_parametric_chan_data *orig) +{ + /* + * LINPOW is defined as: + * y = (a * x + b) ^ g | x >= d + * y = c * x | 0 <= x < d + * + * First, compute y for the cross-over point x=d, get + * y1 = c * d and y1 = (a * d + b) ^ g. If y1 and y2 are not equal, + * the curve is not continuous, and cannot be inverted. Since they must + * be equal, choose the cross-over point as y = c * d. + * + * Solve x from the first equation: + * x = (y ^ (1/g) - b) / a = 1/a * y ^ (1/g) - b/a + * + * Solve x from the second equation: + * x = y / c + * + * The result can be parametrized into POWLIN. + */ + + /* Ensure the inequalities do not need reversing, and invertibility */ + if (orig->c < 1e-6f) + return false; + + /* Invertibility conditions */ + if (fabsf(orig->a) < 1e-6f) + return false; + + if (fabsf(orig->g) < 1e-6f) + return false; + + /* Continuity condition */ + float y1 = orig->c * orig->d; + float y2 = powf(orig->a * orig->d + orig->b, orig->g); + if (!(fabsf(y1 - y2) < 1e-5)) + return false; + + /* inv is a POWLIN curve, orig is LINPOW */ + inv->a = 1.0f / orig->a; + inv->b = -orig->b / orig->a; + inv->c = 1.0f / orig->c; + inv->d = orig->c * orig->d; + inv->g = 1.0f / orig->g; + + return true; +} + +static bool +invert_powlin(union weston_color_curve_parametric_chan_data *inv, + const union weston_color_curve_parametric_chan_data *orig) +{ + /* + * POWLIN is defined as: + * y = a * x ^ g + b | x >= d + * y = c * x | 0 <= x < d + * + * First, compute y for the cross-over point x=d, get + * y1 = c * d and y1 = a * d ^ g + b. If y1 and y2 are not equal, + * the curve is not continuous, and cannot be inverted. Since they must + * be equal, choose the cross-over point as y = c * d. + * + * Solve x from the first equation: + * x = ((y - b) / a) ^ (1/g) = (1/a * y - b/a) ^ (1/g) + * + * Solve x from the second equation: + * x = y / c + * + * The result can be parametrized into LINPOW. + */ + + /* Ensure the inequalities do not need reversing, and invertibility */ + if (orig->c < 1e-6f) + return false; + + /* Invertibility conditions */ + if (fabsf(orig->a) < 1e-6f) + return false; + + if (fabsf(orig->g) < 1e-6f) + return false; + + /* Continuity condition */ + float y1 = orig->c * orig->d; + float y2 = orig->a * powf(orig->d, orig->g) + orig->b; + if (!(fabsf(y1 - y2) < 1e-5)) + return false; + + /* inv is a LINPOW curve, orig is POWLIN */ + inv->a = 1.0f / orig->a; + inv->b = -orig->b / orig->a; + inv->c = 1.0f / orig->c; + inv->d = orig->c * orig->d; + inv->g = 1.0f / orig->g; + + return true; +} + +static bool +weston_color_curve_parametric_inverse(struct weston_color_curve_parametric *inv, + const struct weston_color_curve_parametric *orig) +{ + unsigned i; + + /* + * Just an assumption that for clamped curves the domain equals range. + * Would be difficult to express otherwise. + */ + inv->clamped_input = orig->clamped_input; + + switch (orig->type) { + case WESTON_COLOR_CURVE_PARAMETRIC_TYPE_LINPOW: + inv->type = WESTON_COLOR_CURVE_PARAMETRIC_TYPE_POWLIN; + for (i = 0; i < ARRAY_LENGTH(inv->params.chan); i++) { + if (!invert_linpow(&inv->params.chan[i], &orig->params.chan[i])) + return false; + } + break; + case WESTON_COLOR_CURVE_PARAMETRIC_TYPE_POWLIN: + inv->type = WESTON_COLOR_CURVE_PARAMETRIC_TYPE_LINPOW; + for (i = 0; i < ARRAY_LENGTH(inv->params.chan); i++) { + if (!invert_powlin(&inv->params.chan[i], &orig->params.chan[i])) + return false; + } + break; + } + + return true; +} + +/** Invert an enumerated or parametric curve + * + * @param curve The curve to be inverted, must be of type identity, enum or + * parametric. LUT_3x1D will fail. + * @return A newly malloc'd curve, or NULL on failure to invert the given curve. + * The resulting curve is of the same type as the given curve. The caller is + * responsible for freeing the returned pointer. + */ +WL_EXPORT struct weston_color_curve * +weston_color_curve_create_inverse(const struct weston_color_curve *curve) +{ + struct weston_color_curve *invcurve; + + invcurve = xzalloc(sizeof *invcurve); + + switch (curve->type) { + case WESTON_COLOR_CURVE_TYPE_IDENTITY: + invcurve->type = WESTON_COLOR_CURVE_TYPE_IDENTITY; + return invcurve; + case WESTON_COLOR_CURVE_TYPE_LUT_3x1D: + /* These may not be possible to invert precisely enough */ + break; + case WESTON_COLOR_CURVE_TYPE_ENUM: + invcurve->type = WESTON_COLOR_CURVE_TYPE_ENUM; + invcurve->u.enumerated.tf = curve->u.enumerated.tf; + switch (curve->u.enumerated.tf_direction) { + case WESTON_FORWARD_TF: + invcurve->u.enumerated.tf_direction = WESTON_INVERSE_TF; + return invcurve; + case WESTON_INVERSE_TF: + invcurve->u.enumerated.tf_direction = WESTON_FORWARD_TF; + return invcurve; + } + break; + case WESTON_COLOR_CURVE_TYPE_PARAMETRIC: + invcurve->type = WESTON_COLOR_CURVE_TYPE_PARAMETRIC; + if (weston_color_curve_parametric_inverse(&invcurve->u.parametric, + &curve->u.parametric)) + return invcurve; + break; + } + + free(invcurve); + return NULL; +} diff --git a/libweston/color-operations.h b/libweston/color-operations.h index 73d7568ef..47f0d5dbd 100644 --- a/libweston/color-operations.h +++ b/libweston/color-operations.h @@ -1,5 +1,5 @@ /* - * Copyright 2025 Collabora, Ltd. + * Copyright 2025-2026 Collabora, Ltd. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -38,4 +38,10 @@ weston_color_curve_sample(struct weston_compositor *compositor, struct weston_vec3f *out, size_t len); +const struct weston_color_curve * +weston_color_transform_as_single_curve(const struct weston_color_transform *xform); + +struct weston_color_curve * +weston_color_curve_create_inverse(const struct weston_color_curve *curve); + #endif /* WESTON_COLOR_OPERATIONS_H */ diff --git a/libweston/renderer-gl/gl-renderer-internal.h b/libweston/renderer-gl/gl-renderer-internal.h index e55fc767c..a15574f05 100644 --- a/libweston/renderer-gl/gl-renderer-internal.h +++ b/libweston/renderer-gl/gl-renderer-internal.h @@ -353,6 +353,7 @@ struct gl_texture_parameters { }; struct gl_shader; +struct gl_shader_blender; struct weston_color_transform; struct dmabuf_allocator; @@ -782,4 +783,15 @@ gl_shader_config_set_color_effect(struct gl_renderer *gr, const char * weston_output_cvd_type_to_str(struct weston_cvd_correction cvd); +void +gl_shader_blender_destroy(struct gl_shader_blender *shader_blender); + +struct gl_shader_blender * +gl_shader_blender_create(struct gl_renderer *gr, struct weston_output *output); + +void +gl_shader_config_set_blender(struct gl_renderer *gr, + struct gl_shader_config *sconf, + const struct gl_shader_blender *shader_blender); + #endif /* GL_RENDERER_INTERNAL_H */ diff --git a/libweston/renderer-gl/gl-renderer.c b/libweston/renderer-gl/gl-renderer.c index 819b6374b..a88431f1e 100644 --- a/libweston/renderer-gl/gl-renderer.c +++ b/libweston/renderer-gl/gl-renderer.c @@ -1,6 +1,6 @@ /* * Copyright © 2012 Intel Corporation - * Copyright © 2015,2019,2021 Collabora, Ltd. + * Copyright © 2015,2019,2021,2026 Collabora, Ltd. * Copyright © 2016 NVIDIA Corporation * * Permission is hereby granted, free of charge, to any person obtaining @@ -182,6 +182,8 @@ struct gl_output_state { GLuint shadow_tex; GLuint shadow_fb; + struct gl_shader_blender *shader_blender; + /* struct gl_renderbuffer::link */ struct wl_list renderbuffer_list; }; @@ -1745,6 +1747,8 @@ prepare_solid_draw(struct gl_shader_config *sconf, return false; } + gl_shader_config_set_blender(gr, sconf, go->shader_blender); + return true; } @@ -1953,6 +1957,8 @@ prepare_textured_draw(struct gl_shader_config *sconf, return false; } + gl_shader_config_set_blender(gr, sconf, go->shader_blender); + color_rep = weston_fill_color_representation(&pnode->surface->color_representation, buffer->pixel_format); @@ -2364,12 +2370,13 @@ draw_mesh(struct gl_renderer *gr, bool opaque) { struct gl_surface_state *gs = get_surface_state(pnode->surface); + struct gl_output_state *go = get_output_state(pnode->output); struct gl_buffer_state *gb = gs->buffer; GLint swizzle_a; assert(nidx > 0); - set_blend_state(gr, !opaque || pnode->view->alpha < 1.0); + set_blend_state(gr, (!opaque || pnode->view->alpha < 1.0) && !go->shader_blender); /* Prevent translucent surfaces from punching holes through the * renderbuffer. */ @@ -2683,6 +2690,16 @@ out: pixman_region32_fini(&repaint); } +static void +maybe_framebuffer_fetch_barrier(struct weston_output *output) +{ + struct gl_renderer *gr = get_renderer(output->compositor); + struct gl_output_state *go = get_output_state(output); + + if (go->shader_blender) + gr->framebuffer_fetch_barrier(); +} + static void repaint_views(struct weston_output *output, pixman_region32_t *damage) { @@ -2697,8 +2714,10 @@ repaint_views(struct weston_output *output, pixman_region32_t *damage) wl_list_for_each_reverse(pnode, &output->paint_node_z_order_list, z_order_link) { if (pnode->plane == &output->primary_plane || - pnode->need_hole) + pnode->need_hole) { + maybe_framebuffer_fetch_barrier(output); draw_paint_node(pnode, damage); + } } glDisableVertexAttribArray(SHADER_ATTRIB_LOC_POSITION); @@ -3090,6 +3109,7 @@ gl_renderer_repaint_output(struct weston_output *output, assert(renderbuffer || go->egl_surface != EGL_NO_SURFACE); assert(output->from_blend_to_output_by_backend || output->color_outcome->from_blend_to_output == NULL || + go->shader_blender || shadow_exists(go)); area_y = is_y_flipped(go) ? @@ -4783,6 +4803,8 @@ gl_renderer_output_create(struct weston_output *output, struct gl_output_state *go; struct gl_renderer *gr = get_renderer(output->compositor); const struct weston_testsuite_quirks *quirks; + bool needs_fb_curves; + bool needs_shadow; int i; assert(!get_output_state(output)); @@ -4811,15 +4833,24 @@ gl_renderer_output_create(struct weston_output *output, go->render_sync = EGL_NO_SYNC_KHR; - if ((output->color_outcome->from_blend_to_output != NULL && - output->from_blend_to_output_by_backend == false) || - quirks->gl_force_full_redraw_of_shadow_fb) { - assert(gl_features_has(gr, FEATURE_COLOR_TRANSFORMS)); + needs_shadow = quirks->gl_force_full_redraw_of_shadow_fb; + needs_fb_curves = output->color_outcome->from_blend_to_output && + !output->from_blend_to_output_by_backend; - go->shadow_format = - pixel_format_get_info(DRM_FORMAT_ABGR16161616F); + if (needs_fb_curves) + weston_assert_true(gr->compositor, gl_features_has(gr, FEATURE_COLOR_TRANSFORMS)); + + if (!needs_shadow && needs_fb_curves) { + go->shader_blender = gl_shader_blender_create(gr, output); + if (go->shader_blender) + weston_log("Output %s uses in-shader blending.\n", output->name); + else + needs_shadow = true; } + if (needs_shadow) + go->shadow_format = pixel_format_get_info(DRM_FORMAT_ABGR16161616F); + wl_list_init(&go->renderbuffer_list); output->renderer_state = go; @@ -4981,6 +5012,8 @@ gl_renderer_output_destroy(struct weston_output *output) if (shadow_exists(go)) gl_fbo_texture_fini(&go->shadow_fb, &go->shadow_tex); + gl_shader_blender_destroy(go->shader_blender); + eglMakeCurrent(gr->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, gr->egl_context); diff --git a/libweston/renderer-gl/gl-shader-config-color-transformation.c b/libweston/renderer-gl/gl-shader-config-color-transformation.c index 70f9da26b..b225df00a 100644 --- a/libweston/renderer-gl/gl-shader-config-color-transformation.c +++ b/libweston/renderer-gl/gl-shader-config-color-transformation.c @@ -1,5 +1,5 @@ /* - * Copyright 2021 Collabora, Ltd. + * Copyright 2021,2026 Collabora, Ltd. * Copyright 2021 Advanced Micro Devices, Inc. * * Permission is hereby granted, free of charge, to any person obtaining @@ -31,10 +31,12 @@ #include #include "color.h" +#include "color-operations.h" #include "color-properties.h" #include "gl-renderer.h" #include "gl-renderer-internal.h" +#include "shared/xalloc.h" #include "shared/weston-assert.h" #include "shared/weston-egl-ext.h" @@ -63,6 +65,12 @@ struct gl_renderer_color_effect { union gl_shader_config_color_effect u; }; +/** for in-shader blending */ +struct gl_shader_blender { + struct gl_renderer_color_curve fb_fetch_curve; + struct gl_renderer_color_curve fb_store_curve; +}; + static void gl_renderer_color_curve_fini(struct gl_renderer_color_curve *gl_curve) { @@ -281,6 +289,7 @@ gl_color_curve_init(struct gl_renderer *gr, }; return true; case WESTON_COLOR_CURVE_TYPE_LUT_3x1D: + weston_assert_ptr_not_null(gr->compositor, xform); return gl_color_curve_lut_3x1d(gr, gl_curve, curve, xform); case WESTON_COLOR_CURVE_TYPE_PARAMETRIC: return gl_color_curve_parametric(gr, gl_curve, curve); @@ -566,3 +575,71 @@ gl_shader_config_set_color_effect(struct gl_renderer *gr, return true; } + +void +gl_shader_blender_destroy(struct gl_shader_blender *shader_blender) +{ + if (!shader_blender) + return; + + gl_renderer_color_curve_fini(&shader_blender->fb_fetch_curve); + gl_renderer_color_curve_fini(&shader_blender->fb_store_curve); + free(shader_blender); +} + +struct gl_shader_blender * +gl_shader_blender_create(struct gl_renderer *gr, struct weston_output *output) +{ + struct gl_shader_blender *shader_blender; + struct weston_color_curve *fb_fetch; + const struct weston_color_curve *fb_store; + const struct weston_color_transform *xform; + bool ok; + + if (!gl_features_has(gr, FEATURE_SHADER_BLENDING)) + return NULL; + + xform = output->color_outcome->from_blend_to_output; + if (!xform) + return NULL; + + fb_store = weston_color_transform_as_single_curve(xform); + if (!fb_store) + return NULL; + + fb_fetch = weston_color_curve_create_inverse(fb_store); + if (!fb_fetch) + return NULL; + + shader_blender = xzalloc(sizeof *shader_blender); + ok = gl_color_curve_init(gr, &shader_blender->fb_store_curve, fb_store, NULL) && + gl_color_curve_init(gr, &shader_blender->fb_fetch_curve, fb_fetch, NULL); + free(fb_fetch); + + if (!ok) { + free(shader_blender); + return NULL; + } + + return shader_blender; +} + +void +gl_shader_config_set_blender(struct gl_renderer *gr, + struct gl_shader_config *sconf, + const struct gl_shader_blender *shader_blender) +{ + if (shader_blender) { + sconf->req.shader_blending = true; + + sconf->req.fb_fetch_curve = shader_blender->fb_fetch_curve.type; + sconf->fb_fetch_curve = shader_blender->fb_fetch_curve.u; + + sconf->req.fb_store_curve = shader_blender->fb_store_curve.type; + sconf->fb_store_curve = shader_blender->fb_store_curve.u; + } else { + sconf->req.shader_blending = false; + sconf->req.fb_fetch_curve = SHADER_COLOR_CURVE_IDENTITY; + sconf->req.fb_store_curve = SHADER_COLOR_CURVE_IDENTITY; + } +}