Timing frame display

Now that we know that we want to draw in frames, the question arises: how do we decide when to draw a new frame?

Some criteria:

  • smoothness – how high a frame rate can we sustain?
  • latency – when we get an event, how quickly can we display that on the screen?
  • reliability – if we push latency to a minimum, will we occasionally skip a frame?
  • responsiveness – can we still respond to other things going on while rendering?

Vertical blanking is important concept in frame scheduling, The contents of video memory (the “front buffer”) are constantly being read out and used to refresh the screen display. The point at which we finish at the bottom of the screen and return to the top is the ideal point to update the contents of the screen from one frame to the next. There is actually a gap (“vertical blanking interval”) where no screen read-out is done. By updating the contents during that interval we avoid tearing.

Vertical Blanking

One way to do frame updates is a “buffer swap”: during the vertical blanking interval we change the address that the graphics card is scanning out from to point to the buffer we were rendering into (the “back buffer”) and then reuse the old front buffer as a new back buffer.

The simplest timing algorithm is to start handling the next frame as soon as possible – as soon as the buffer swaps completes. Ideally, we have a way of doing the buffer swap asynchronously. We tell the X server to swap buffers, then go off and do our other business (queue up input, handle D-BUS requests, map application windows, etc.) while we are waiting for the swap to complete. This looks like:

Drawing as soon as possible

What’s marked as “Render Scene” in the diagram includes all the steps of the frame processing described in my last post: processing queued input, updating animations, doing layout, and then rendering.

Unfortunately, standard GL API’s don’t give us an easy way of doing the asynchronous vblank-synced swap shown above. If we only have a blocking buffer swap operation, then the picture looks more like:

Blocking SwapBuffers

So we never actually have any “idle time” – we are either processing a frame or blocking for the swap to complete. This isn’t the end of the world (we still process pending events before starting frame rendering), but it will reduce the overall responsiveness of the system. So, one thing we can do is to try to predict the correct time to start frame rendering so we complete just before vblank:

Delaying Frame Rendering

This also has a secondary benefit of reducing latency a bit. The downside is that we have to guess how long the frame rendering will take (a good guess is as long as the previous frame), and if we guess wrong, we miss the vblank, producing a stutter in the output. Using a safety factor and allowing for, say, a 50% longer rendering time than the last frame reduces the chance of this.

Finally, what should we do if we don’t have any support for VBlank synchronization at all. Then the only real approach is to simply pick a frame rate, and try to render frames at that rate:

No VBlank

Picking the frame rate to use in this case is a little tricky. You probably don’t want to use the exact frame rate of the display, since that will result in tearing at a consistent point on the screen, which is quite obvious. Using a slightly slower frame rate may produce visually superior results.

To be continued… the next (and I think last) part of this series will cover how to extend frame timing from the compositing manager to the application. Things get more complex when we have three entities involved.

Note: although I’ve shown the X server doing the buffer swaps, the considerations in this post are exactly the same if the compositing manager is talking directly to the kernel graphics driver to do buffer swaps; there’s nothing actually X-specific about this.

Frames not Idles

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:

Timeline of updates without frame synchronization

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:

Timeline of updates synchronized to frames

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.

GNOME Shell SOC Projects

Just wanted to quickly mention the three Google Summer of Code proposals that were accepted related to GNOME Shell.

David Jordan is going to work on Application-aware Window Management . This is a really interesting proposal to allow applications to expose more content to the desktop window switching than just a single toplevel; for example, to make it possible to look at all of the tabs of your web browser and switch directly to one of them. Marina will be the mentor the project.

Nathan Lo is going to work on Multiple monitor support for workspaces. The combination of multiple monitors and multiple workspaces has always been uncomfortable in GNOME. Switching all your monitors at once just isn’t what you normally want. It’s more likely that you want to keep one monitor fixed and switch another. This problem becomes more pressing in GNOME Shell where we’ve tried to make workspaces easy to use and intuitive. So the project will explore how it works to have separate workspaces on the different monitors and what the appropriate way is to present that to the user. I’ll mentor this one.

And finally, Siegfried Gevatter is going going to work on Zeitgeist Integration. Currently GNOME Shell has a very simple flat list of recent documents in the overview. We’ve wanted to move to a more time-base “journal” approach, and possibly add extra features like tagging. That is very similar to what the Zeitgeist project is about, so rather than duplicating effort, it would be very nice to just take the data that Zeitgeist is collecting and show it in the GNOME Shell user interface. This will be mentored by Seif from the Zeitgeist project.

Reinteract 0.5.0

Reinteract notebooks - cross platform

People who have been reading my blog for a while may remember Reinteract. I haven’t written anything about it here for a while, but I’ve still been working on it; usually in a spurts of a few weeks of evening hacking at a time. I’m going to be talking about Reinteract at Pycon this weekend, which inspired me to go ahead and finish up something worth calling a release. Reinteract 0.5.0: Source, installers for Windows and OS X.

One big change over the last year is the notebook user interface. Any decent size project is going to have multiple worksheets along with the Python libraries and data files that they use. The Reinteract main window now represents a notebook – an entire project – instead of a single worksheet.

Another major effort was porting Reinteract to Windows and OS X. Since Python and GTK+ already worked on these platforms, it was mostly a question of putting together existing pieces. But there was quite a bit of work to get things installed properly, and to create slick installers that combine Reinteract with the libraries and Python modules it depends upon. On OS X, I also wrote some native code to get a proper global menu.

With the new version, the work of computing a Reinteract worksheet occurs in a separate thread; this allows the user interface to stay responsive during long running operations. And allows you to interrupt running operations, which is a very handy thing to have if you accidentally write an infinite loop, or start some calculation that’s going to take a year to finish.

And of course, there are lots of bug fixes and small features as well. I’ve had useful suggestions, bug reports, and patches from a great number of people. I’d like to thank Kai Willadsen and Jon Kuhn in particular for their active contributions. Among other things, Kai recently landed a change that makes the replot module considerably more useful: instead of being restricted to a single plot command, you can use all the methods of the matplotlib Axes object.

GNOME Shell Status – 2009-02-09

It’s time for another GNOME Shell status report. Let me start off with a screenshot of what it looks like today:

gnome-shell-20090209

The most obvious change visible here since my last post are workspaces. The static image doesn’t real do it justice. As you add and remove workspaces they slide in and off the screen with a slick animation. (It’s even better than that movie now.) You can drag and drop applications between windows or drag an application or recent document from the sidebar to a workspace to launch it. The workspaces are mostly Dan’s work, though I spent a day or so adding the window dragging. (And then Dan cleaned that up into a nice reusable DND framework that we can use all over the place.)

Marina added the recent documents section to the sidebar. As you can see, the documents get appropriate thumbnails:

gnome-shell-recent-documents

More recently she’s been working on adding the ability to expand the sidebar sections to the full screen so you can browse More applications or files.

Colin, taking a break from saving the world for D-BUS, added a user menu to the right side of the panel, based on Jon McCann’s applet from GDM. Now you can actually log out, switch users, etc, from within gnome-shell.

gnome-shell-user-menu

Colin also did a quick hack to get the current GNOME taskbar to show up at the bottom of the screen so that you can see minimized applications and appications flashing for urgent attention. We’d like to do something better, or at least visually slicker, in the long term, but this fills in a big hole in the abiity to just use GNOME shell day-to-day.

Jonathan Matthew took visual effects that had been done upstream for Mutter and moved them to our Tweener and Javascript framework. This involved some Mutter patches to keep the different effects from stomping on each other. Again, it’s not something I really can take a static screenshot of… you’ll have to build GNOME Shell yourself to try it out, but there are animations now for windows mapping, maximizing, minimizing, being closed and so forth.

The largest number of contributions so far have been to a unsurprising place – to the build setup script: William Lachance, Mads Villadsen, Siegfried Gevatter, and Timbobsteve have all contributed to it. It now figures out what packages you need to install for your distribution ahead of time, instead of breaking obscurely halfway through, making it easier than ever to try GNOME Shell out. Thanks also to Achim Frase for testing and bug reporting.

There’s some more interesting work in the pipeline – enhancements to the “More” views, monitoring the applications that the user is using and so forth. See our Todo for ideas about ways you could help, or join us on IRC in #gnome-shell  and ask questions.