gallium/vl: Implement compositor shader-based alpha blending

Implements shader-based global blending and pre-multiplied alpha support
to YUV compositing, allowing for transparent overlays and alpha-channel
based transparency with RGBA overlays.

Handle pre-multiplied alpha images by un-multiplying the pre-multiplied
alpha colours, to allow for straight-alpha (which is easier to
implement) to be applied.

Thanks nyanmisaka for the help, and for pointing out the difference
between pre-multiplied alpha and straight alpha.

Thanks David Rosca and Benjamin Cheng for improvements to the code and
spotting errors.

Closes: https://gitlab.freedesktop.org/mesa/mesa/-/work_items/12977

Signed-off-by: Thong Thai <thong.thai@amd.com>
Reviewed-by: David Rosca <david.rosca@amd.com>
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/41090>
This commit is contained in:
Thong Thai 2026-05-08 15:36:43 +00:00 committed by Marge Bot
parent cfd2d9f411
commit c96c7e77f7
3 changed files with 104 additions and 8 deletions

View file

@ -872,7 +872,7 @@ vl_compositor_init_state(struct vl_compositor_state *s, struct pipe_context *pip
pipe->screen,
PIPE_BIND_CONSTANT_BUFFER,
PIPE_USAGE_DEFAULT,
sizeof(vl_csc_matrix) * 3 + 28 * sizeof(float) + 4 * sizeof(int)
sizeof(vl_csc_matrix) * 3 + 29 * sizeof(float) + 5 * sizeof(int)
);
if (!s->shader_params)

View file

@ -107,6 +107,11 @@ struct vl_compositor_layer
void *samplers[3];
void *blend;
/* for the CS compositor */
bool blend_enabled;
float blend_alpha;
unsigned blend_mode;
struct pipe_sampler_view *sampler_views[3];
struct {
struct vertex2f tl, br;

View file

@ -46,6 +46,9 @@ struct cs_viewport {
float chroma_offset_y;
float proj[2][4];
float chroma_proj[2][4];
int blend_enabled;
int blend_mode;
float blend_alpha;
};
struct cs_shader {
@ -55,7 +58,7 @@ struct cs_shader {
unsigned num_samplers;
nir_variable *samplers[3];
nir_variable *image;
nir_def *params[17];
nir_def *params[18];
nir_def *fone;
nir_def *fzero;
};
@ -96,6 +99,9 @@ static nir_def *cs_create_shader(struct vl_compositor *c, struct cs_shader *s)
vec4 chroma_proj[2]; // params[9-10]
vec4 rgb2yuv[3]; // params[11-13]
vec4 primaries[3]; // params[14-16]
int blend_enabled; // params[17].x
int blend_mode; // params[17].y
float blend_alpha; // params[17].z
};
void main()
@ -417,6 +423,53 @@ static inline nir_def *cs_fetch_texel(struct cs_shader *s, nir_def *coords, unsi
.texture_deref = tex_deref, .sampler_deref = tex_deref);
}
static inline nir_def *cs_apply_blend(struct cs_shader *s, nir_def *color, nir_def *dst_color, nir_def *alpha, nir_def *dst_alpha)
{
/*
int blend_enabled = params[17].x;
float global_alpha = params[17].z;
float alpha = alpha ? alpha : 1.0;
alpha *= global_alpha;
blended = dst_color * (1.0 - alpha) + color * alpha;
if (dst_alpha)
blended.w = dst_alpha;
color = blend_enabled ? blended : color;
return color;
*/
nir_builder *b = &s->b;
nir_def *blend_enabled = nir_channel(b, s->params[17], 0);
nir_def *global_alpha = nir_channel(b, s->params[17], 2);
alpha = alpha ? alpha : s->fone;
alpha = nir_fmul(b, alpha, global_alpha);
alpha = nir_replicate(b, alpha, color->num_components);
nir_def *blended = nir_flrp(b, dst_color, color, alpha);
if (dst_alpha)
blended = nir_vector_insert_imm(b, blended, dst_alpha, 3);
color = nir_bcsel(b, nir_ine_imm(b, blend_enabled, 0), blended, color);
return color;
}
static inline nir_def *cs_premultiplied_to_straight_alpha(struct cs_shader *s, nir_def *color, nir_def *alpha)
{
/*
if (blend_mode & PIPE_VIDEO_VPP_BLEND_MODE_PREMULTIPLIED_ALPHA)
return color / alpha;
return color;
*/
nir_builder *b = &s->b;
nir_def *blend_mode = nir_channel(b, s->params[17], 1);
nir_def *has_premultiplied_alpha =
nir_ine_imm(b, nir_iand_imm(b, blend_mode, PIPE_VIDEO_VPP_BLEND_MODE_PREMULTIPLIED_ALPHA), 0);
nir_def *straight_alpha_color = nir_fdiv(b, color, nir_replicate(b, alpha, color->num_components));
straight_alpha_color = nir_fsat(b, straight_alpha_color);
return nir_bcsel(b, has_premultiplied_alpha, straight_alpha_color, color);
}
static inline nir_def *cs_image_load(struct cs_shader *s, nir_def *pos)
{
/*
@ -480,13 +533,23 @@ static void *create_video_buffer_shader(struct vl_compositor *c)
nir_def *col[3];
for (unsigned i = 0; i < 3; ++i)
col[i] = cs_fetch_texel(&s, pos[MIN2(i, 1)], i);
nir_def *alpha = nir_channel(b, col[0], 3);
nir_def *color = nir_vec4(b, col[0], col[1], col[2], s.fone);
color = cs_premultiplied_to_straight_alpha(&s, color, alpha);
for (unsigned i = 0; i < 3; ++i)
col[i] = cs_color_conversion(&s, color, i, YUV2RGB);
color = nir_vec4(b, col[0], col[1], col[2], s.fone);
color = cs_prim_trc_conversion(&s, color);
color = nir_vector_insert_imm(b, color, alpha, 3);
nir_def *dst_color = cs_image_load(&s, cs_translate(&s, ipos));
nir_def *dst_alpha = nir_channel(b, dst_color, 3);
color = cs_apply_blend(&s, color, dst_color, alpha, dst_alpha);
cs_image_store(&s, cs_translate(&s, ipos), color);
return cs_create_shader_state(c, &s);
@ -522,17 +585,21 @@ static void *create_yuv_progressive_shader(struct vl_compositor *c, enum vl_comp
color = nir_vec4(b, col[0], col[1], col[2], s.fone);
nir_def *dst_color = cs_image_load(&s, cs_translate(&s, ipos));
if (plane != VL_COMPOSITOR_PLANE_UV) {
unsigned c = 0;
if (plane == VL_COMPOSITOR_PLANE_U)
c = 1;
else if (plane == VL_COMPOSITOR_PLANE_V)
c = 2;
color = nir_channel(b, color, c);
nir_def *src = nir_channel(b, color, c);
nir_def *dst = nir_channel(b, dst_color, c);
color = cs_apply_blend(&s, src, dst, NULL, NULL);
} else {
nir_def *col1 = nir_channel(b, color, 1);
nir_def *col2 = nir_channel(b, color, 2);
color = nir_vec2(b, col1, col2);
nir_def *src = nir_vec2(b, nir_channel(b, color, 1), nir_channel(b, color, 2));
nir_def *dst = nir_vec2(b, nir_channel(b, dst_color, 0), nir_channel(b, dst_color, 1));
color = cs_apply_blend(&s, src, dst, NULL, NULL);
}
cs_image_store(&s, cs_translate(&s, ipos), color);
@ -550,10 +617,12 @@ static void *create_rgb_yuv_shader(struct vl_compositor *c, enum vl_compositor_p
nir_def *ipos = cs_create_shader(c, &s);
nir_def *color = NULL;
nir_def *alpha = NULL;
if (plane == VL_COMPOSITOR_PLANE_Y) {
nir_def *pos = cs_tex_coords(&s, ipos, COORDS_LUMA);
color = cs_fetch_texel(&s, pos, 0);
alpha = nir_channel(b, color, 3);
} else {
/*
vec2 pos[4];
@ -594,24 +663,34 @@ static void *create_rgb_yuv_shader(struct vl_compositor *c, enum vl_compositor_p
nir_def *c = cs_fetch_texel(&s, pos[i], 0);
color = color ? nir_fadd(b, color, c) : c;
nir_def *a = nir_channel(b, c, 3);
alpha = alpha ? nir_fadd(b, alpha, a) : a;
}
color = nir_fmul_imm(b, color, 0.25f);
alpha = nir_fmul_imm(b, alpha, 0.25f);
}
color = cs_premultiplied_to_straight_alpha(&s, color, alpha);
color = nir_vector_insert_imm(b, color, s.fone, 3);
color = cs_prim_trc_conversion(&s, color);
nir_def *dst_color = cs_image_load(&s, cs_translate(&s, ipos));
if (plane != VL_COMPOSITOR_PLANE_UV) {
unsigned c = 0;
if (plane == VL_COMPOSITOR_PLANE_U)
c = 1;
else if (plane == VL_COMPOSITOR_PLANE_V)
c = 2;
color = cs_color_conversion(&s, color, c, RGB2YUV);
nir_def *src = cs_color_conversion(&s, color, c, RGB2YUV);
nir_def *dst = nir_channel(b, dst_color, c);
color = cs_apply_blend(&s, src, dst, alpha, NULL);
} else {
nir_def *col1 = cs_color_conversion(&s, color, 1, RGB2YUV);
nir_def *col2 = cs_color_conversion(&s, color, 2, RGB2YUV);
color = nir_vec2(b, col1, col2);
nir_def *src = nir_vec2(b, col1, col2);
nir_def *dst = nir_vec2(b, nir_channel(b, dst_color, 0), nir_channel(b, dst_color, 1));
color = cs_apply_blend(&s, src, dst, alpha, NULL);
}
cs_image_store(&s, cs_translate(&s, ipos), color);
@ -992,6 +1071,12 @@ set_viewport(struct vl_compositor_state *s,
memcpy(ptr_float, &s->primaries, sizeof(vl_csc_matrix));
ptr_float += sizeof(vl_csc_matrix) / sizeof(float);
ptr_int = (int *)ptr_float;
*ptr_int++ = drawn->blend_enabled;
*ptr_int++ = drawn->blend_mode;
ptr_float = (float *)ptr_int;
*ptr_float++ = drawn->blend_alpha;
pipe_buffer_unmap(s->pipe, buf_transfer);
return true;
@ -1025,6 +1110,12 @@ draw_layers(struct vl_compositor *c,
drawn.chroma_clamp_y = (float)sampler1->texture->height0 * layer->src.br.y - 0.5;
drawn.chroma_offset_x = chroma_offset_x(s->chroma_location);
drawn.chroma_offset_y = chroma_offset_y(s->chroma_location);
drawn.blend_enabled = layer->blend_enabled;
drawn.blend_mode = layer->blend_mode;
if (layer->blend_mode & PIPE_VIDEO_VPP_BLEND_MODE_GLOBAL_ALPHA)
drawn.blend_alpha = layer->blend_alpha;
else
drawn.blend_alpha = 1.0f;
calc_proj(layer, samplers[0]->texture, drawn.proj);
calc_proj(layer, sampler1->texture, drawn.chroma_proj);
set_viewport(s, &drawn, samplers);