The topic of how GTK+ draws the content of a window is a fairly complex one; it involves drilling down from
GdkWindow, to Cairo, to the windowing system currently in use. This task can seem somewhat daunting, even for people that are familiar with the GTK+ API from an application development standpoint, so I decided to write down a quick introduction of how GTK+ draws, going from widgets, to windows, to surfaces, to native windowing resources.
How it starts
GTK+ always draws because something asked it to. This request may come from the windowing system — for instance, because the window manager presented your application window to the user, or because the user resized it — but more often it’ll come from a widget updating its contents. Let’s say, a progress bar going from 50% to 60%; or a label, changing its text; or a spinner, doing a new iteration. This request invalidates the backing
GdkWindow of the widget — which usually it’s the
GdkWindow of the top-level
GtkWindow that contains the widget. Each invalidation carries with itself the region of the window to be invalidated (the “damage”), so that when we get to actually drawing, we know which parts of the window need to be updated, and we can avoid drawing outside of the damaged areas.
Race the clock
The first invalidation will start the “frame clock”; this clock is an object that keeps track of each phase inside a frame, like painting windows, laying out widgets, or processing the event queue. This allows GTK+ to be synchronized to things like the windowing system compositor, and to avoid performing unnecessary work that won’t be seen by the user — for instance, drawing something at 1000 frames per second when your display can only run at 60 Hz.
Once the clock reaches the “paint” phase, we process all the scheduled updates on a window; this will cause a
GDK_EXPOSE event to be emitted. The
GDK_EXPOSE event contains the
GdkWindow that needs to be updated, and the union of all the invalidated areas. It’s important to note that, by and large, only top level windows will receive a
GDK_EXPOSE event; for historical reasons, though, some widgets may apply a particular event mask that will cause
GDK_EXPOSE events to be delivered to them as well. You should not write code that depends on that, and if you have legacy code ported from older versions of GTK+ 2.x you should really consider dropping the
GDK_EXPOSURE_MASK from the event mask.
GTK+ takes the window and invalidated region out of the
GDK_EXPOSE event and figures out which top level widget they belong to. Once that’s found, GTK+ will begin the actual rendering process. First of all, GTK+ will ask the
GdkWindow to create a buffer where to draw the contents of the window; the buffer is going to be clipped to the region that needs to be drawn, and will be cleared with the background color of the window. GDK will create a “drawing context” — a transient object that keeps track of things like OpenGL and Cairo drawing. Then, GTK+ will ask the widget to draw itself using a Cairo context. For leaf widgets this means drawing themeselves on that context; for container widgets, this additionally means recursing through all their children. At the end of this process, GTK+ will end the frame by telling GDK to take the buffer that contains all the rendered widgets and use it to replace the current contents of the window. GDK will then ask the windowing system to present the window to the user, whenever it’s more appropriate.
The process outlined above has various caveats, and the code that deals with invalidation and validation of windows inside GDK is fairly complex; it also has a long history, which means that its API is littered by the headstones of ages past.
Before GTK+ 3.0, for instance, you were supposed to handle the “expose” events yourself, and create a Cairo context to draw on a widget by using
gdk_cairo_create(); this has long since been unnecessary, because the
GtkWidget::draw virtual function already provides us with a Cairo context with which to draw. The
gdk_cairo_create() function, though, has been deprecated in GTK+ 3.22, and should not be used in newly written code; if you need a Cairo context you should create a similar Cairo surface, call
cairo_create() on it, and then use the surface as the source for the Cairo context that GTK+ provides to you when drawing a widget. On the other hand, if you were using
gdk_cairo_create() to draw on a top-level, native
GdkWindow in response to a
GDK_EXPOSE event then you should use the newly added
GdkDrawingContext API instead.
The internals of the drawing code in GTK+ have been progressively updated over the years, to cope with things like new windowing systems, as well as other rendering API. It’s fairly certain that they will change again, especially when it comes to improving the rendering performance. Many of the changes that may seem arbitrary are, in reality, stepping stones towards reducing the time spent inside the toolkit in each frame, and leave more time to the application logic.