GNOME Bugzilla – Bug 640195
gdk_cairo_create gets cairo context with badly clipped cairo_surface_t.
Last modified: 2011-01-23 20:45:37 UTC
gdk_cairo_create, when invoked on a widget embedded in a window, can result in a cairo_t containing a badly clipped cairo_surface_t. Subsequent drawing to this cairo context is truncated. The problem appears when gdk_cairo_create needs to create a cairo context for a widget. (I think it only needs to do this, if the underlying window doesn't already have a cairo context, but I'm not sure. So this problem should only appear when manually updating a widget.) To create the context, gtk_cairo_create first creates a cairo surface to do the drawing on by calling gdk_window_create_cairo_surface using the widget's window as an argument. gdk_window_create_cairo_surface seems to be trying to do the right thing. It creates a cairo surface from the window using the window's ref_cairo_surface method, then wraps this in a subsurface clipped to the outline of the widget. So it should cope fine if the root surface corresponds to the entire X11 window. Unfortunately, the ref_cairo_surface method - gdk_x11_ref_cairo_surface in this case - gets it wrong; it creates a new x11 cairo surface using the widget's visual etc, which I assume correspond to the top-level X11 window, but constrains its size to the size of the widget rather than the size of the top-level window. So at the end of the day we have a cairo surface that can only render to the top-left corner of the top-level X11 window. If this doesn't encompass the widget - which it won't unless the widget is in the top-left corner - then we won't be able to draw to the widget. One easy way to fix this is to make sure we use the toplevel window when getting the root cairo surface. This corresponds with replacing the second line of gdk_window_create_cairo_surface as follows: -- @@ -3546,7 +3547,7 @@ gdk_window_create_cairo_surface (GdkWindow *window, { cairo_surface_t *surface, *subsurface; - surface = gdk_window_ref_impl_surface (window); + surface = gdk_window_ref_impl_surface (gdk_window_get_effective_toplevel(window)); if (gdk_window_has_impl (window)) return surface; -- But I'm pretty sure that isn't the proper way to fix this issue:-( This problem is affecting gnomine in gnome-games. (See gtk_mine_draw, minefield.c:429.) Problem only occurs when you click on the GtkMineField widget, not when it is first rendered.
P.S., I could probably fix this myself with a few hints. E.g., should gdk_window_create_cairo_surface be responsible for creating the surface from the top-level window as in the above patch? That seems to make sense, as this function knows about surfaces and subsurfaces and how they map to windows. But that will leave gdk_x11_ref_cairo_surface broken. Alternatively, we could fix this in gdk_x11_ref_cairo_surface (and probably its brethren). But then the interface is a bit random. gdk_x11_ref_cairo_surface returns a surface for the underlying drawable rather than for the widget. But maybe that's OK.
Created attachment 179060 [details] [review] Fix for gnomine abnormally-clipped cairo surface bug. Read more of the GDK code, in particular the comment about impl windows. That suggests the attached patch. This fixes gnomine.
commit 51290e0a57ec10ad966cf2e1a17b90d66eb330ea Author: Benjamin Otte <otte@redhat.com> Date: Sun Jan 23 21:39:00 2011 +0100 gdk: When reffing the impl surface, ref it from the impl window This was causing surfaces to be created with the wrong size and that caused broken clipping.
That was a tough one. Still, gnomine should still only draw from a draw callback and not call gdk_cairo_create(). You're bypassing all the smarts built into GTK (like double buffering to avoid flicker) the way the code is currently written. From looking at the gnomine code, this should be quite easy to achieve, too, by using gtk_widget_queue_draw().