st/theme-node-drawing: Factor in border radius in update_resources()

The reduced border radius from st_theme_node_reduce_border_radius() depends
on the allocation size, and when it changes, has_large_corners can change.
has_large_corners is used to determine whether to pre-render using cairo,
so that means a change to the allocation size can influence whether the
pre-render using cairo should happen or not.

On changes to the allocation size we don't want to call render_resources()
though, but just update_resources() instead. And since we know now that
changes to the allocation size might cause pre-rendering to get enabled
or disabled, we need to do the "should pre-render" checks in
update_resources(), too.

So factor out those "should pre-render" checks into a new function
st_theme_node_maybe_prerender_background(), and then call that function from
both render_resources() and update_resources().

This fixes rendering artifacts with borders or rounded corners after changes
to the actor size.

Closes: https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/6567
Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/3354>
This commit is contained in:
Jonas Dreßler 2024-06-06 18:13:27 +03:00 committed by Marge Bot
parent dc6f7a1289
commit e1868cab1a

View File

@ -1362,6 +1362,92 @@ st_theme_node_prerender_background (StThemeNode *node,
return texture;
}
static void
st_theme_node_maybe_prerender_background (StThemeNodePaintState *state,
StThemeNode *node,
float width,
float height,
float resource_scale)
{
gboolean has_border;
gboolean has_border_radius;
gboolean has_inset_box_shadow;
gboolean has_large_corners;
StShadow *box_shadow_spec;
box_shadow_spec = st_theme_node_get_box_shadow (node);
has_inset_box_shadow = box_shadow_spec && box_shadow_spec->inset;
if (node->border_width[ST_SIDE_TOP] > 0 ||
node->border_width[ST_SIDE_LEFT] > 0 ||
node->border_width[ST_SIDE_RIGHT] > 0 ||
node->border_width[ST_SIDE_BOTTOM] > 0)
has_border = TRUE;
else
has_border = FALSE;
if (node->border_radius[ST_CORNER_TOPLEFT] > 0 ||
node->border_radius[ST_CORNER_TOPRIGHT] > 0 ||
node->border_radius[ST_CORNER_BOTTOMLEFT] > 0 ||
node->border_radius[ST_CORNER_BOTTOMRIGHT] > 0)
has_border_radius = TRUE;
else
has_border_radius = FALSE;
/* The cogl code pads each corner to the maximum border radius,
* which results in overlapping corner areas if the radius
* exceeds the actor's halfsize, causing rendering errors.
* Fall back to cairo in these cases. */
has_large_corners = FALSE;
if (has_border_radius) {
guint border_radius[4];
int corner;
st_theme_node_reduce_border_radius (node, width, height, border_radius);
for (corner = 0; corner < 4; corner ++) {
if (border_radius[corner] * 2 > height ||
border_radius[corner] * 2 > width) {
has_large_corners = TRUE;
break;
}
}
}
state->corner_material[ST_CORNER_TOPLEFT] =
st_theme_node_lookup_corner (node, width, height, resource_scale, ST_CORNER_TOPLEFT);
state->corner_material[ST_CORNER_TOPRIGHT] =
st_theme_node_lookup_corner (node, width, height, resource_scale, ST_CORNER_TOPRIGHT);
state->corner_material[ST_CORNER_BOTTOMRIGHT] =
st_theme_node_lookup_corner (node, width, height, resource_scale, ST_CORNER_BOTTOMRIGHT);
state->corner_material[ST_CORNER_BOTTOMLEFT] =
st_theme_node_lookup_corner (node, width, height, resource_scale, ST_CORNER_BOTTOMLEFT);
/* Use cairo to prerender the node if there is a gradient, or
* background image with borders and/or rounded corners,
* or large corners, since we can't do those things
* easily with cogl.
*
* FIXME: if we could figure out ahead of time that a
* background image won't overlap with the node borders,
* then we could use cogl for that case.
*/
if ((node->background_gradient_type != ST_GRADIENT_NONE)
|| (has_inset_box_shadow && (has_border || node->background_color.alpha > 0))
|| (st_theme_node_get_background_image (node) && (has_border || has_border_radius))
|| has_large_corners)
{
state->prerendered_texture = st_theme_node_prerender_background (node, width, height, resource_scale);
if (state->prerendered_texture)
state->prerendered_pipeline = _st_create_texture_pipeline (state->prerendered_texture);
else
state->prerendered_pipeline = NULL;
}
}
static void st_theme_node_paint_borders (StThemeNodePaintState *state,
ClutterPaintNode *node,
const ClutterActorBox *box,
@ -1487,10 +1573,7 @@ st_theme_node_render_resources (StThemeNodePaintState *state,
float height,
float resource_scale)
{
gboolean has_border;
gboolean has_border_radius;
gboolean has_inset_box_shadow;
gboolean has_large_corners;
StShadow *box_shadow_spec;
g_return_if_fail (width > 0 && height > 0);
@ -1512,72 +1595,7 @@ st_theme_node_render_resources (StThemeNodePaintState *state,
box_shadow_spec = st_theme_node_get_box_shadow (node);
has_inset_box_shadow = box_shadow_spec && box_shadow_spec->inset;
if (node->border_width[ST_SIDE_TOP] > 0 ||
node->border_width[ST_SIDE_LEFT] > 0 ||
node->border_width[ST_SIDE_RIGHT] > 0 ||
node->border_width[ST_SIDE_BOTTOM] > 0)
has_border = TRUE;
else
has_border = FALSE;
if (node->border_radius[ST_CORNER_TOPLEFT] > 0 ||
node->border_radius[ST_CORNER_TOPRIGHT] > 0 ||
node->border_radius[ST_CORNER_BOTTOMLEFT] > 0 ||
node->border_radius[ST_CORNER_BOTTOMRIGHT] > 0)
has_border_radius = TRUE;
else
has_border_radius = FALSE;
/* The cogl code pads each corner to the maximum border radius,
* which results in overlapping corner areas if the radius
* exceeds the actor's halfsize, causing rendering errors.
* Fall back to cairo in these cases. */
has_large_corners = FALSE;
if (has_border_radius) {
guint border_radius[4];
int corner;
st_theme_node_reduce_border_radius (node, width, height, border_radius);
for (corner = 0; corner < 4; corner ++) {
if (border_radius[corner] * 2 > height ||
border_radius[corner] * 2 > width) {
has_large_corners = TRUE;
break;
}
}
}
state->corner_material[ST_CORNER_TOPLEFT] =
st_theme_node_lookup_corner (node, width, height, resource_scale, ST_CORNER_TOPLEFT);
state->corner_material[ST_CORNER_TOPRIGHT] =
st_theme_node_lookup_corner (node, width, height, resource_scale, ST_CORNER_TOPRIGHT);
state->corner_material[ST_CORNER_BOTTOMRIGHT] =
st_theme_node_lookup_corner (node, width, height, resource_scale, ST_CORNER_BOTTOMRIGHT);
state->corner_material[ST_CORNER_BOTTOMLEFT] =
st_theme_node_lookup_corner (node, width, height, resource_scale, ST_CORNER_BOTTOMLEFT);
/* Use cairo to prerender the node if there is a gradient, or
* background image with borders and/or rounded corners,
* or large corners, since we can't do those things
* easily with cogl.
*
* FIXME: if we could figure out ahead of time that a
* background image won't overlap with the node borders,
* then we could use cogl for that case.
*/
if ((node->background_gradient_type != ST_GRADIENT_NONE)
|| (has_inset_box_shadow && (has_border || node->background_color.alpha > 0))
|| (st_theme_node_get_background_image (node) && (has_border || has_border_radius))
|| has_large_corners)
state->prerendered_texture = st_theme_node_prerender_background (node, width, height,
resource_scale);
if (state->prerendered_texture)
state->prerendered_pipeline = _st_create_texture_pipeline (state->prerendered_texture);
else
state->prerendered_pipeline = NULL;
st_theme_node_maybe_prerender_background (state, node, width, height, resource_scale);
if (box_shadow_spec && !has_inset_box_shadow)
{
@ -1616,14 +1634,12 @@ st_theme_node_update_resources (StThemeNodePaintState *state,
float height,
float resource_scale)
{
gboolean had_prerendered_texture = FALSE;
gboolean had_box_shadow = FALSE;
StShadow *box_shadow_spec;
g_return_if_fail (width > 0 && height > 0);
/* Free handles we can't reuse */
had_prerendered_texture = (state->prerendered_texture != NULL);
g_clear_object (&state->prerendered_texture);
if (state->prerendered_pipeline != NULL)
@ -1645,20 +1661,7 @@ st_theme_node_update_resources (StThemeNodePaintState *state,
box_shadow_spec = st_theme_node_get_box_shadow (node);
if (had_prerendered_texture)
{
state->prerendered_texture = st_theme_node_prerender_background (node, width, height, resource_scale);
state->prerendered_pipeline = _st_create_texture_pipeline (state->prerendered_texture);
}
else
{
int corner_id;
for (corner_id = 0; corner_id < 4; corner_id++)
if (state->corner_material[corner_id] == NULL)
state->corner_material[corner_id] =
st_theme_node_lookup_corner (node, width, height, resource_scale, corner_id);
}
st_theme_node_maybe_prerender_background (state, node, width, height, resource_scale);
if (had_box_shadow)
state->box_shadow_pipeline = _st_create_shadow_pipeline (box_shadow_spec,