GTKEventBox clip rectangle is much bigger than allocation, nearly size of whole screen
Description
Take a GTKEventBox and place a child widget inside it. On the next call to size_allocate
, it recomputes its internal clipping rectangle priv->clip
used for painting. The bug is that this rectangle is far too big.
It's perhaps easiest to illustrate this bug with an example - Inkscape:
There is one GTKEventBox in this window. It's the splitter for the pane to the right of the canvas, highlighted in red.
The red rectangle is also the size allocation, which is therefore correctly calculated. (So this isn't #2556.) However, the internal clipping rectangle priv->clip
is not correctly calculated. Instead of being equal to the red rectangle, it looks more like this:
This is a disaster for performance. The oversized clipping region causes Cairo painting to be performed over the OpenGL content in the canvas, triggering all sorts of slow code paths within GDK that would better be avoided.
Possible explanation and solution
Disclaimer: I admit that I don't understand this part of GTK completely, but the explanations and fix below at least paint a consistent picture.
In Inkscape, the GTKEventBox's child widget is a GTKImage of the same size (for showing the grip handle). The following chain of events leads it to calculate the wrong clipping rectangle:
-
First,
gtk_event_box_size_allocate
is called. This starts by setting the GTKEventBox's allocation (to the correct value). -
Then it computes the child's allocation via
if (!gtk_widget_get_has_window (widget)) { child_allocation.x = allocation->x; child_allocation.y = allocation->y; } else { child_allocation.x = 0; child_allocation.y = 0; } child_allocation.width = allocation->width; child_allocation.height = allocation->height;
It takes the bottom path, meaning
child_allocation
is translated to the origin. This is the intended behaviour, because the child lives inside a separate window created by the GTKEventBox, and so the origin of that window is indeed the correct place for the child to be because that's the position of the GTKEventBox. This value is then storedBut it will be a problem later. -
Later, at the end of
gtk_widget_set_clip
, the following snippet is run:priv->clip = *clip; while (priv->parent && _gtk_widget_get_window (widget) == _gtk_widget_get_window (priv->parent)) { GtkWidgetPrivate *parent_priv = priv->parent->priv; GdkRectangle union_rect; gdk_rectangle_union (&priv->clip, &parent_priv->clip, &union_rect); if (gdk_rectangle_equal (&parent_priv->clip, &union_rect)) break; parent_priv->clip = union_rect; // <--- (!) priv = parent_priv; }
The
widget
in question here, and its associatedpriv
, are those of the child Image widget. The snippet propagates the clip rectangle of the child up the tree to its parents, enlarging their clipping rectangles to contain its own. The immediate parent is the GtkEventBox.
It's now clear what goes wrong. The child's clipping rectangle is at (0, 0) because it lives in the coordinate system of the GtkEventBox's internal window, but it's being used as if it's in the coordinate system of the parent, which is different. This results in the clipping rectangle for the parent GtkEventBox to be enlarged to contain the origin, as in the picture.
To confirm this, I commented out the line marked (!)
and it made the problem go away. Specifically, I could see that Inkscape was no longer triggering the slow code path reported as a separate bug in #4704, caused when something overdraws the canvas.
If I knew better what I was doing, I could come up with a more robust solution than the above. Hopefully, a GTK developer will be able to do exactly that.
Version info
GTK 3.24.29 (debug build of 77f32a69)
Inkscape 1.2 alpha (local development branch 6f1e0c40)