The classic update cycle in GTK+ has very little overall coordination. If the widget hierarchy needs layout, we add an “idle function” to do that at the next opportunity. If a widget needs a redraw, we add an idle for that. If we want to animate something, we add a timer function to update the animation at 20 frames per second. When we receive a mouse event we go ahead and process it. The end result looks something like:
There’s all sorts of redundant work going on that the user never sees; we lay out several times before redrawing. We update the animation or the mouse position several times in a row. And then we finally draw something. A better way to handle things is to organize around frames; we want to synchronize things so that we do each type of update exactly once before we redraw:
So, how close are we to that ideal with Clutter? I lied a bit above: GTK+ was fixed a few years ago to bind together relayout and redraw: every time GTK+ does a layout, it does a redraw immediately after. This makes sure that, when you are resizing a window, GTK+ doesn’t spend all its time laying out the contents and never get around to redrawing them. Clutter copied that approach and also binds relayout and redraw together. Beyond that, the Clutter 1.0-integration branch now has work by Emmanuele Bassi to bind animation into the frame cycle as well.
Motion compression. The less obvious thing that a frame based approach provides is a theoretically sound basis for motion compression. Motion events have always been a bit of a problem; if you are doing anything at all expensive in response to a motion event, you can build up a huge backlog of pending motion events. The user drags an object around, the user releases the mouse button, and the object keeps going on its own for a few seconds.
Various approaches have been taken to this: PointerMotionHintMask is an X mechanism where only a single motion is sent until the application acknowledges it; this had advantages for not flooding 1980’s era networks, but introduces latency and complexity. Clutter currently rate-limits motion events by time; with this approach either too many or too few events may be dropped, and it’s possible to drop the last event and never catch up to the current mouse position when the user stops moving the mouse.
With frames it all becomes easy: you just queue up input events until you are ready to draw a frame. Then you run through that queue and drop any motion event where the next event is also a motion event.
I’ve tried implementing this technique in gnome-shell (by queueing and processing events before sending them on to Clutter) and it works well. But it should be generally applicable to all Clutter applications.
To be continued… So how should the frame drawing be scheduled? when do we start drawing a new frame? How does it relate VBlank synchronization? I’ll cover that in a follow-up post.