diff -ruN a/include/render/gles2.h b/include/render/gles2.h --- a/include/render/gles2.h 2026-07-07 23:46:09.000000000 +0200 +++ b/include/render/gles2.h 2026-08-31 12:49:04.008068765 +0200 @@ -35,6 +35,12 @@ GLint tex; GLint alpha; GLint pos_attrib; + /* PaganDE: rounded clip */ + GLint rounded; + GLint box_pos; + GLint box_size; + GLint radius; + GLint corners; }; struct wlr_gles2_renderer { diff -ruN a/include/wlr/render/pass.h b/include/wlr/render/pass.h --- a/include/wlr/render/pass.h 2026-07-07 23:46:09.000000000 +0200 +++ b/include/wlr/render/pass.h 2026-08-31 12:49:04.007446160 +0200 @@ -84,6 +84,14 @@ WLR_SCALE_FILTER_NEAREST, }; +/* PaganDE: rounded corners bitmask (see corner_radius below) */ +enum wlr_rounded_corner { + WLR_ROUNDED_CORNER_TOP_LEFT = 1 << 0, + WLR_ROUNDED_CORNER_TOP_RIGHT = 1 << 1, + WLR_ROUNDED_CORNER_BOTTOM_RIGHT = 1 << 2, + WLR_ROUNDED_CORNER_BOTTOM_LEFT = 1 << 3, +}; + struct wlr_render_texture_options { /* Source texture */ struct wlr_texture *texture; @@ -95,6 +103,12 @@ const float *alpha; /* Clip region, leave NULL to disable clipping */ const pixman_region32_t *clip; + /* PaganDE: rounded corners. When corner_radius > 0 and rounded_corners != 0, + * the texture is clipped to a rectangle with rounded corners (bitmask of + * enum wlr_rounded_corner). Renderers that do not support this feature + * must ignore these fields. */ + float corner_radius; + uint32_t rounded_corners; /* Transform applied to the source texture */ enum wl_output_transform transform; /* Filtering */ diff -ruN a/include/wlr/types/wlr_scene.h b/include/wlr/types/wlr_scene.h --- a/include/wlr/types/wlr_scene.h 2026-07-07 23:46:09.000000000 +0200 +++ b/include/wlr/types/wlr_scene.h 2026-08-31 12:49:04.007705726 +0200 @@ -215,6 +215,11 @@ // True if the underlying buffer is a wlr_single_pixel_buffer_v1 bool is_single_pixel_buffer; + + // PaganDE: rounded clip applied when rendering this buffer + // (see wlr_scene_buffer_set_rounded_clip) + float clip_radius; + uint32_t clip_corners; // If is_single_pixel_buffer is set, contains the color of the buffer // as {R, G, B, A} where the max value of each component is UINT32_MAX uint32_t single_pixel_buffer_color[4]; @@ -549,6 +554,16 @@ enum wl_output_transform transform); /** + * PaganDE: clip the buffer to a rectangle with rounded corners. + * + * radius is the corner radius in output pixels; corners is a bitmask of + * enum wlr_rounded_corner. Passing radius 0 (or corners 0) disables the clip. + * Renderers without support (e.g. pixman) ignore this. + */ +void wlr_scene_buffer_set_rounded_clip(struct wlr_scene_buffer *scene_buffer, + float radius, uint32_t corners); + +/** * Sets the opacity of this buffer */ void wlr_scene_buffer_set_opacity(struct wlr_scene_buffer *scene_buffer, diff -ruN a/render/gles2/pass.c b/render/gles2/pass.c --- a/render/gles2/pass.c 2026-07-07 23:46:09.000000000 +0200 +++ b/render/gles2/pass.c 2026-08-31 12:53:51.197412809 +0200 @@ -247,6 +247,19 @@ set_proj_matrix(shader->proj, pass->projection_matrix, &dst_box); set_tex_matrix(shader->tex_proj, options->transform, &src_fbox); + bool rounded = options->corner_radius > 0 && options->rounded_corners != 0; + glUniform1f(shader->rounded, rounded ? 1.0f : 0.0f); + if (rounded) { + glUniform2f(shader->box_pos, dst_box.x, dst_box.y); + glUniform2f(shader->box_size, dst_box.width, dst_box.height); + glUniform1f(shader->radius, options->corner_radius); + glUniform4f(shader->corners, + (options->rounded_corners & WLR_ROUNDED_CORNER_TOP_LEFT) ? 1.0f : 0.0f, + (options->rounded_corners & WLR_ROUNDED_CORNER_TOP_RIGHT) ? 1.0f : 0.0f, + (options->rounded_corners & WLR_ROUNDED_CORNER_BOTTOM_RIGHT) ? 1.0f : 0.0f, + (options->rounded_corners & WLR_ROUNDED_CORNER_BOTTOM_LEFT) ? 1.0f : 0.0f); + } + render(&dst_box, options->clip, shader->pos_attrib); glBindTexture(texture->target, 0); diff -ruN a/render/gles2/renderer.c b/render/gles2/renderer.c --- a/render/gles2/renderer.c 2026-07-07 23:46:09.000000000 +0200 +++ b/render/gles2/renderer.c 2026-08-31 12:49:04.008364833 +0200 @@ -652,6 +652,11 @@ renderer->shaders.tex_rgba.tex = glGetUniformLocation(prog, "tex"); renderer->shaders.tex_rgba.alpha = glGetUniformLocation(prog, "alpha"); renderer->shaders.tex_rgba.pos_attrib = glGetAttribLocation(prog, "pos"); + renderer->shaders.tex_rgba.rounded = glGetUniformLocation(prog, "u_rounded"); + renderer->shaders.tex_rgba.box_pos = glGetUniformLocation(prog, "u_box_pos"); + renderer->shaders.tex_rgba.box_size = glGetUniformLocation(prog, "u_box_size"); + renderer->shaders.tex_rgba.radius = glGetUniformLocation(prog, "u_radius"); + renderer->shaders.tex_rgba.corners = glGetUniformLocation(prog, "u_corners"); renderer->shaders.tex_rgbx.program = prog = link_program(renderer, common_vert_src, tex_rgbx_frag_src); @@ -663,6 +668,11 @@ renderer->shaders.tex_rgbx.tex = glGetUniformLocation(prog, "tex"); renderer->shaders.tex_rgbx.alpha = glGetUniformLocation(prog, "alpha"); renderer->shaders.tex_rgbx.pos_attrib = glGetAttribLocation(prog, "pos"); + renderer->shaders.tex_rgbx.rounded = glGetUniformLocation(prog, "u_rounded"); + renderer->shaders.tex_rgbx.box_pos = glGetUniformLocation(prog, "u_box_pos"); + renderer->shaders.tex_rgbx.box_size = glGetUniformLocation(prog, "u_box_size"); + renderer->shaders.tex_rgbx.radius = glGetUniformLocation(prog, "u_radius"); + renderer->shaders.tex_rgbx.corners = glGetUniformLocation(prog, "u_corners"); if (renderer->exts.OES_egl_image_external) { renderer->shaders.tex_ext.program = prog = @@ -675,6 +685,11 @@ renderer->shaders.tex_ext.tex = glGetUniformLocation(prog, "tex"); renderer->shaders.tex_ext.alpha = glGetUniformLocation(prog, "alpha"); renderer->shaders.tex_ext.pos_attrib = glGetAttribLocation(prog, "pos"); + renderer->shaders.tex_ext.rounded = glGetUniformLocation(prog, "u_rounded"); + renderer->shaders.tex_ext.box_pos = glGetUniformLocation(prog, "u_box_pos"); + renderer->shaders.tex_ext.box_size = glGetUniformLocation(prog, "u_box_size"); + renderer->shaders.tex_ext.radius = glGetUniformLocation(prog, "u_radius"); + renderer->shaders.tex_ext.corners = glGetUniformLocation(prog, "u_corners"); } pop_gles2_debug(renderer); diff -ruN a/render/gles2/shaders/tex_external.frag b/render/gles2/shaders/tex_external.frag --- a/render/gles2/shaders/tex_external.frag 2026-07-07 23:46:09.000000000 +0200 +++ b/render/gles2/shaders/tex_external.frag 2026-08-31 12:53:34.209558298 +0200 @@ -10,6 +10,51 @@ uniform samplerExternalOES texture0; uniform float alpha; + +// PaganDE: rounded clip (discard fragments outside the rounded rect) +// GLSL ES 1.00: tylko floaty (bez bitowych operacji); maska rogów w vec4. +uniform float u_rounded; +uniform vec2 u_box_pos; +uniform vec2 u_box_size; +uniform float u_radius; +uniform vec4 u_corners; /* x=TL y=TR z=BR w=BL, 1.0 = zaokrąglony */ + +void rounded_clip() { + if (u_rounded == 0.0) { + return; + } + vec2 p = gl_FragCoord.xy - u_box_pos; + float r = u_radius; + /* top-left */ + if (u_corners.x > 0.5) { + if (p.x <= r && p.y <= r && distance(p, vec2(r, r)) > r) { + discard; + } + } + /* top-right */ + if (u_corners.y > 0.5) { + if (p.x >= u_box_size.x - r && p.y <= r + && distance(p, vec2(u_box_size.x - r, r)) > r) { + discard; + } + } + /* bottom-right */ + if (u_corners.z > 0.5) { + if (p.x >= u_box_size.x - r && p.y >= u_box_size.y - r + && distance(p, vec2(u_box_size.x - r, u_box_size.y - r)) > r) { + discard; + } + } + /* bottom-left */ + if (u_corners.w > 0.5) { + if (p.x <= r && p.y >= u_box_size.y - r + && distance(p, vec2(r, u_box_size.y - r)) > r) { + discard; + } + } +} + void main() { + rounded_clip(); gl_FragColor = texture2D(texture0, v_texcoord) * alpha; } diff -ruN a/render/gles2/shaders/tex_rgba.frag b/render/gles2/shaders/tex_rgba.frag --- a/render/gles2/shaders/tex_rgba.frag 2026-07-07 23:46:09.000000000 +0200 +++ b/render/gles2/shaders/tex_rgba.frag 2026-08-31 12:53:34.209518897 +0200 @@ -8,6 +8,51 @@ uniform sampler2D tex; uniform float alpha; + +// PaganDE: rounded clip (discard fragments outside the rounded rect) +// GLSL ES 1.00: tylko floaty (bez bitowych operacji); maska rogów w vec4. +uniform float u_rounded; +uniform vec2 u_box_pos; +uniform vec2 u_box_size; +uniform float u_radius; +uniform vec4 u_corners; /* x=TL y=TR z=BR w=BL, 1.0 = zaokrąglony */ + +void rounded_clip() { + if (u_rounded == 0.0) { + return; + } + vec2 p = gl_FragCoord.xy - u_box_pos; + float r = u_radius; + /* top-left */ + if (u_corners.x > 0.5) { + if (p.x <= r && p.y <= r && distance(p, vec2(r, r)) > r) { + discard; + } + } + /* top-right */ + if (u_corners.y > 0.5) { + if (p.x >= u_box_size.x - r && p.y <= r + && distance(p, vec2(u_box_size.x - r, r)) > r) { + discard; + } + } + /* bottom-right */ + if (u_corners.z > 0.5) { + if (p.x >= u_box_size.x - r && p.y >= u_box_size.y - r + && distance(p, vec2(u_box_size.x - r, u_box_size.y - r)) > r) { + discard; + } + } + /* bottom-left */ + if (u_corners.w > 0.5) { + if (p.x <= r && p.y >= u_box_size.y - r + && distance(p, vec2(r, u_box_size.y - r)) > r) { + discard; + } + } +} + void main() { + rounded_clip(); gl_FragColor = texture2D(tex, v_texcoord) * alpha; } diff -ruN a/render/gles2/shaders/tex_rgbx.frag b/render/gles2/shaders/tex_rgbx.frag --- a/render/gles2/shaders/tex_rgbx.frag 2026-07-07 23:46:09.000000000 +0200 +++ b/render/gles2/shaders/tex_rgbx.frag 2026-08-31 12:53:34.209462266 +0200 @@ -8,6 +8,51 @@ uniform sampler2D tex; uniform float alpha; + +// PaganDE: rounded clip (discard fragments outside the rounded rect) +// GLSL ES 1.00: tylko floaty (bez bitowych operacji); maska rogów w vec4. +uniform float u_rounded; +uniform vec2 u_box_pos; +uniform vec2 u_box_size; +uniform float u_radius; +uniform vec4 u_corners; /* x=TL y=TR z=BR w=BL, 1.0 = zaokrąglony */ + +void rounded_clip() { + if (u_rounded == 0.0) { + return; + } + vec2 p = gl_FragCoord.xy - u_box_pos; + float r = u_radius; + /* top-left */ + if (u_corners.x > 0.5) { + if (p.x <= r && p.y <= r && distance(p, vec2(r, r)) > r) { + discard; + } + } + /* top-right */ + if (u_corners.y > 0.5) { + if (p.x >= u_box_size.x - r && p.y <= r + && distance(p, vec2(u_box_size.x - r, r)) > r) { + discard; + } + } + /* bottom-right */ + if (u_corners.z > 0.5) { + if (p.x >= u_box_size.x - r && p.y >= u_box_size.y - r + && distance(p, vec2(u_box_size.x - r, u_box_size.y - r)) > r) { + discard; + } + } + /* bottom-left */ + if (u_corners.w > 0.5) { + if (p.x <= r && p.y >= u_box_size.y - r + && distance(p, vec2(r, u_box_size.y - r)) > r) { + discard; + } + } +} + void main() { + rounded_clip(); gl_FragColor = vec4(texture2D(tex, v_texcoord).rgb, 1.0) * alpha; } diff -ruN a/types/scene/wlr_scene.c b/types/scene/wlr_scene.c --- a/types/scene/wlr_scene.c 2026-07-07 23:46:09.000000000 +0200 +++ b/types/scene/wlr_scene.c 2026-08-31 12:49:04.007960213 +0200 @@ -1124,6 +1124,18 @@ } } +void wlr_scene_buffer_set_rounded_clip(struct wlr_scene_buffer *scene_buffer, + float radius, uint32_t corners) { + if (scene_buffer->clip_radius == radius + && scene_buffer->clip_corners == corners) { + return; + } + assert(radius >= 0); + scene_buffer->clip_radius = radius; + scene_buffer->clip_corners = corners; + scene_node_update(&scene_buffer->node, NULL); +} + void wlr_scene_buffer_set_opacity(struct wlr_scene_buffer *scene_buffer, float opacity) { if (scene_buffer->opacity == opacity) { @@ -1499,6 +1511,26 @@ case WLR_SCENE_NODE_BUFFER:; struct wlr_scene_buffer *scene_buffer = wlr_scene_buffer_from_node(node); + bool rounded = scene_buffer->clip_radius > 0 + && scene_buffer->clip_corners != 0; + if (rounded) { + /* Corner areas are no longer opaque: exclude them from the + * opaque region so occlusion and blending stay correct. */ + int r = (int)scene_buffer->clip_radius; + pixman_region32_t corners; + pixman_region32_init(&corners); + pixman_region32_union_rect(&corners, &corners, + x, y, r, r); + pixman_region32_union_rect(&corners, &corners, + x + dst_box.width - r, y, r, r); + pixman_region32_union_rect(&corners, &corners, + x + dst_box.width - r, y + dst_box.height - r, r, r); + pixman_region32_union_rect(&corners, &corners, + x, y + dst_box.height - r, r, r); + pixman_region32_subtract(&opaque, &opaque, &corners); + pixman_region32_fini(&corners); + } + if (scene_buffer->is_single_pixel_buffer) { // 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){ @@ -1544,9 +1576,11 @@ .dst_box = dst_box, .transform = transform, .clip = &render_region, + .corner_radius = scene_buffer->clip_radius, + .rounded_corners = scene_buffer->clip_corners, .alpha = &scene_buffer->opacity, .filter_mode = scene_buffer->filter_mode, - .blend_mode = !data->output->scene->calculate_visibility || + .blend_mode = rounded || !data->output->scene->calculate_visibility || !pixman_region32_empty(&opaque) ? WLR_RENDER_BLEND_MODE_PREMULTIPLIED : WLR_RENDER_BLEND_MODE_NONE, .transfer_function = scene_buffer->transfer_function,