🔒 Repository is read-only – file editing is disabled.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
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,