stage: Track overlay damage per view

The previously painted rectangle of an overlay is not a global state,
but depends on what view it was painted on. There was also an issue
where an overlay being updated but not changing position, e.g. due to a
0,0 pointer movement or an absolute pointer movement with the position
not changing, not properly triggering damage of the old position when it
eventually actually moved.

Fix this by tracking damage per view, while also fixing the state
tracking to handle unchanged positions.

Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/4331>
This commit is contained in:
Jonas Ådahl 2025-03-12 11:56:46 +08:00 committed by Marge Bot
parent dcd89a62e1
commit 1dcece298e

View File

@ -36,6 +36,12 @@ struct _MetaStageWatch
gpointer user_data;
};
typedef struct _MetaOverlayViewState
{
graphene_rect_t painted_rect;
gboolean has_painted_rect;
} MetaOverlayViewState;
struct _MetaOverlay
{
MetaStage *stage;
@ -47,8 +53,8 @@ struct _MetaOverlay
graphene_matrix_t transform;
graphene_rect_t current_rect;
graphene_rect_t previous_rect;
gboolean previous_is_valid;
GHashTable *view_states;
};
struct _MetaStage
@ -75,6 +81,7 @@ meta_overlay_new (MetaStage *stage)
overlay = g_new0 (MetaOverlay, 1);
overlay->stage = stage;
overlay->pipeline = cogl_pipeline_new (ctx);
overlay->view_states = g_hash_table_new_full (NULL, NULL, NULL, g_free);
return overlay;
}
@ -84,6 +91,7 @@ meta_overlay_free (MetaOverlay *overlay)
{
if (overlay->pipeline)
g_object_unref (overlay->pipeline);
g_hash_table_unref (overlay->view_states);
g_free (overlay);
}
@ -113,19 +121,57 @@ meta_overlay_set (MetaOverlay *overlay,
overlay->current_rect = *dst_rect;
}
static MetaOverlayViewState *
get_view_state (MetaOverlay *overlay,
ClutterStageView *view)
{
return g_hash_table_lookup (overlay->view_states, view);
}
static MetaOverlayViewState *
ensure_view_state (MetaOverlay *overlay,
ClutterStageView *view)
{
MetaOverlayViewState *view_state;
view_state = get_view_state (overlay, view);
if (!view_state)
{
view_state = g_new0 (MetaOverlayViewState, 1);
g_hash_table_insert (overlay->view_states, view, view_state);
}
return view_state;
}
static void
meta_overlay_invalidate_views (MetaOverlay *overlay)
{
g_hash_table_remove_all (overlay->view_states);
}
static void
meta_overlay_paint (MetaOverlay *overlay,
ClutterPaintContext *paint_context)
{
ClutterStageView *view;
MetaOverlayViewState *view_state = NULL;
CoglFramebuffer *framebuffer;
if (!overlay->texture)
return;
view = clutter_paint_context_get_stage_view (paint_context);
if (view)
view_state = ensure_view_state (overlay, view);
if (!overlay->is_visible &&
if ((!overlay->texture ||
!overlay->is_visible) &&
!(clutter_paint_context_get_paint_flags (paint_context) &
CLUTTER_PAINT_FLAG_FORCE_CURSORS))
return;
{
if (view_state)
view_state->has_painted_rect = FALSE;
return;
}
framebuffer = clutter_paint_context_get_framebuffer (paint_context);
cogl_framebuffer_draw_rectangle (framebuffer,
@ -137,10 +183,10 @@ meta_overlay_paint (MetaOverlay *overlay,
(overlay->current_rect.origin.y +
overlay->current_rect.size.height));
if (!graphene_rect_equal (&overlay->previous_rect, &overlay->current_rect))
if (view_state)
{
overlay->previous_rect = overlay->current_rect;
overlay->previous_is_valid = TRUE;
view_state->painted_rect = overlay->current_rect;
view_state->has_painted_rect = TRUE;
}
}
@ -347,67 +393,68 @@ meta_stage_new (MetaBackend *backend)
}
static void
queue_cursor_overlay_redraw_clutter_rect (MetaStage *stage,
MetaOverlay *overlay,
graphene_rect_t *rect)
intersect_and_queue_redraw (ClutterStageView *view,
const MtkRectangle *clip)
{
MtkRectangle clip = {
.x = (int) floorf (rect->origin.x),
.y = (int) floorf (rect->origin.y),
.width = (int) ceilf (rect->size.width),
.height = (int) ceilf (rect->size.height)
};
GList *l;
MtkRectangle view_layout;
MtkRectangle view_clip;
clutter_stage_view_get_layout (view, &view_layout);
if (mtk_rectangle_intersect (clip, &view_layout, &view_clip))
{
clutter_stage_view_add_redraw_clip (view, &view_clip);
clutter_stage_view_schedule_update (view);
}
}
static void
cursor_rect_to_clip (const graphene_rect_t *cursor_rect,
MtkRectangle *clip_rect)
{
mtk_rectangle_from_graphene_rect (cursor_rect,
MTK_ROUNDING_STRATEGY_GROW,
clip_rect);
/* Since we're flooring the coordinates, we need to enlarge the clip by the
* difference between the actual coordinate and the floored value */
clip.width += (int) ceilf (rect->origin.x - clip.x) * 2;
clip.height += (int) ceilf (rect->origin.y - clip.y) * 2;
for (l = clutter_stage_peek_stage_views (CLUTTER_STAGE (stage));
l;
l = l->next)
{
ClutterStageView *view = l->data;
MtkRectangle view_layout;
MtkRectangle view_clip;
if (clutter_stage_view_get_default_paint_flags (view) &
CLUTTER_PAINT_FLAG_NO_CURSORS)
continue;
if (meta_stage_view_is_cursor_overlay_inhibited (META_STAGE_VIEW (view)))
continue;
clutter_stage_view_get_layout (view, &view_layout);
if (mtk_rectangle_intersect (&clip, &view_layout, &view_clip))
{
clutter_stage_view_add_redraw_clip (view, &view_clip);
clutter_stage_view_schedule_update (view);
}
}
clip_rect->width += (int) ceilf (cursor_rect->origin.x - clip_rect->x) * 2;
clip_rect->height += (int) ceilf (cursor_rect->origin.y - clip_rect->y) * 2;
}
static void
queue_redraw_for_cursor_overlay (MetaStage *stage,
MetaOverlay *overlay)
{
/* Clear the location the overlay was at before, if we need to. */
if (overlay->previous_is_valid)
{
queue_cursor_overlay_redraw_clutter_rect (stage,
overlay,
&overlay->previous_rect);
overlay->previous_is_valid = FALSE;
}
GList *l;
/* Draw the overlay at the new position */
if (overlay->is_visible && overlay->texture)
for (l = clutter_stage_peek_stage_views (CLUTTER_STAGE (stage));
l;
l = l->next)
{
queue_cursor_overlay_redraw_clutter_rect (stage,
overlay,
&overlay->current_rect);
ClutterStageView *view = CLUTTER_STAGE_VIEW (l->data);
MetaOverlayViewState *view_state;
view_state = ensure_view_state (overlay, view);
if (view_state->has_painted_rect)
{
MtkRectangle clip;
cursor_rect_to_clip (&view_state->painted_rect, &clip);
intersect_and_queue_redraw (view, &clip);
}
if (overlay->is_visible &&
overlay->texture &&
!(clutter_stage_view_get_default_paint_flags (view) &
CLUTTER_PAINT_FLAG_NO_CURSORS) &&
!meta_stage_view_is_cursor_overlay_inhibited (META_STAGE_VIEW (view)))
{
MtkRectangle clip;
cursor_rect_to_clip (&overlay->current_rect, &clip);
intersect_and_queue_redraw (view, &clip);
}
}
}
@ -513,4 +560,8 @@ meta_stage_rebuild_views (MetaStage *stage)
meta_monitor_manager_get_screen_size (monitor_manager, &width, &height);
clutter_actor_set_size (CLUTTER_ACTOR (stage), width, height);
g_list_foreach (stage->overlays,
(GFunc) meta_overlay_invalidate_views,
NULL);
}