9.2. Speeding Up Window Updates

Next, you examine a technique for speeding up window draws that makes use of QPixmap, an offscreen buffer in which you can draw with QPainter. The speed increase comes from realizing that many window redraws are invoked from outside the application and not in response to a need to change the contents of the window.

The technique works like this: You draw your window contents to an offscreen buffer, a QPixmap, and then use a bit-block transfer (or "bitblt", pronounced "bit blit") to copy the buffer to the screen. Whenever you need to update the window, but its contents have not changed, you just bitblt the buffer to the screen again. The bit-block transfer operation is much quicker than redrawing the window contents from scratch, and on most PCs and many workstations, it is made even quicker by specialized hardware designed to perform the task (that is, 2D video accelerators). This technique is called double-buffering.

Let's take a look at this technique in action. Listings 9.1 and 9.2 present a widget call KQuickDraw, which demonstrates double-buffering.


Example 9.1. kquickdraw.h: Class Declaration for KQuickDraw, a Widget That Demonstrates Double-buffering

   1 
   2  1: #ifndef __KQUICKDRAW_H__
   3  2: #define __KQUICKDRAW_H__
   4  3:
   5  4:
   6  5: #include <qwidget.h>
   7  6:
   8  7: class QPixmap;
   9  8:
  10  9: const int NEllipses=1000;
  11 10:
  12 11: /**
  13 12:  * KQuickDraw
  14 13:  * Quickly redraw a window.
  15 14:  **/
  16 15:
  17 16: class KQuickDraw : public QWidget
  18 17: {
  19 18:  public:
  20 19:   KQuickDraw (QWidget *parent, const char *name=0);
  21 20:
  22 21:  protected:
  23 22:   /**
  24 23:    * Repaint the window using a bit-block transfer from the
  25 24:    *  off-screen buffer (a QPixmap). Re-create the pixmap first,
  26 25:    *  if necessary.
  27 26:    **/
  28 27:   void paintEvent (QPaintEvent *);
  29 28:
  30 29:   void resizeEvent (QResizeEvent *);
  31 30:
  32 31:  private:
  33 32:   QPixmap *qpixmap;
  34 33:   bool bneedrecreate;
  35 34:   double x[NEllipses], y[NEllipses];
  36 35:
  37 36: };
  38 37:
  39 38: #endif
  40 

KQuickDraw displays 1,000 randomly placed ellipses in its content area using a double-buffer method. A flag, bneedrecreate, is set to true whenever the window contents need to be re-created. In this program, the window contents need to be re-created (or simply created) when the program first starts and whenever the window is resized. When the window is restored after being minimized, when another window stops obscuring this window, or at other times when paint events are generated, you simply copy (bitblt) the contents of the QPixmap to the screen.


Example 9.2. kquickdraw.cpp: Class Definition for KQuickDraw

   1 
   2  1: #include <qpainter.h>
   3  2: #include <qpixmap.h>
   4  3:
   5  4: #include <kapp.h>
   6  5:
   7  6: #include "kquickdraw.h"
   8  7:
   9  8:
  10  9: KQuickDraw::KQuickDraw (QWidget *parent, const char *name=0) :
  11 10:   QWidget (parent, name)
  12 11: {
  13 12:   bneedrecreate=true;
  14 13:   qpixmap=0;
  15 14:
  16 15:   for (int i=0; i<NEllipses; i++)
  17 16:     {
  18 17:       x[i]=(kapp->random()%100)/100.;
  19 18:       y[i]=(kapp->random()%100)/100.;
  20 19:     }
  21 20:
  22 21:   setBackgroundMode (NoBackground);
  23 22:       
  24 23: }
  25 24: 
  26 25: void
  27 26: KQuickDraw::paintEvent (QPaintEvent *)
  28 27: {
  29 28:
  30 29:   if (bneedrecreate)
  31 30:     {
  32 31:     if (qpixmap!=0)
  33 32:   delete qpixmap;
  34 33:     qpixmap = new QPixmap (width(), height());
  35 34:
  36 35:     QPainter qpainter;
  37 36:     qpainter.begin (qpixmap, this);
  38 37:     qpainter.fillRect (qpixmap->rect(), white);
  39 38:     qpainter.setBrush (blue);
  40 39:     int w = width()/10;
  41 40:     int h = height()/10;
  42 41:     for (int i=0; i<NEllipses; i++)
  43 42:   qpainter.drawEllipse (x[i]*width(), y[i]*height(), w, h);
  44 43:
  45 44:     bneedrecreate=false;
  46 45:     }
  47 46:
  48 47:   bitBlt (this, 0, 0, qpixmap);
  49 48:
  50 49: }
  51 50:
  52 51: void
  53 52: KQuickDraw::resizeEvent (QResizeEvent *)
  54 53: {
  55 54:   bneedrecreate = true;
  56 55: }
  57 

Let's look at paintEvent(); this is where the double-buffering is implemented. Line 29 tests to see whether the pixmap contents need to be re-created and carries out the task if necessary.

Here you have used a QPainter differently than before. Line 36 calls painter.begin() with two arguments. The first is the QPixmap on which you wish to draw, and the second is a pointer to the KQuickDraw widget. Using this form of the begin() method tells the object painter to use the default properties of the window when drawing on the pixmap. These default properties are

  • The current pen color

  • The background color

  • The default font

Previously, when you have used QPainter, you have not called the begin() method at all. Instead, you passed a pointer to the current window to the QPainter constructor, and the begin() method was called automatically.

Finally, the offscreen pixmap is copied to the screen with the Qt function bitBlt() in line 47.

The main() function in Listing 9.3 can be used to try out this widget. You can see it running in Figure 9.1


Example 9.3. main.cpp: A main() Function Suitable for Testing KQuickDraw

   1 
   2  1: #include <kapp.h>
   3  2:
   4  3: #include "kquickdraw.h"
   5  4:
   6  5: int
   7  6: main (int argc, char *argv[])
   8  7: {
   9  8:   KApplication kapplication (argc, argv, "kquickdrawtest");
  10  9:   KQuickDraw *kquickdraw = new KQuickDraw (0);
  11 10:
  12 11:   kapplication.setMainWidget (kquickdraw);
  13 12:
  14 13:   kquickdraw->show();
  15 14:   return kapplication.exec();
  16 15: }
  17 


Figure 9.1. kquickdraw updates its window quickly by storing its window contents in an offscreen buffer.


9.2.1. Experimenting with KQuickDraw

To get an idea of the difference double-buffering makes, comment out line 29 in Listing 9.2.

This will force KQuickDraw to re-create the pixmap every time the paintEvent() method is called. Now turn on opaque moving in KWin using the KDE Control Center. This property is listed under Windows, Properties and is called Display Content in Moving Windows. (Note: while you're at it, turn on Display Content in Resizing Windows, too; you'll use that in a minute.)

Now, start KQuickDraw and maximize the window. Then find another window—a konsole, for example—and drag it around. Notice how the slow update gives a "ghosting" effect so that a partial second copy of the konsole window is visible during the move operation. (If you don't see this—well, then your computer is too fast! Try the experiment again with NEllipses set to 5000 or 10000. Just don't get the impression that you'll never need double-buffering. First, your users' computers might not be as fast as yours. Second, when you create applications, you may find that you are drawing things that take much longer than drawing ellipses.)

Remove the comment from line 29 and try the experiment again. Is it better this time?

9.2.2. Flicker-free Updates

A second advantage exists to using double-buffering. That is, you can avoid some of the "flicker" that occurs when you draw multiple objects on a window. This flicker occurs because the scene the user sees may change rapidly as new objects are added to the window.

Double-buffering won't help with the other major source of flicker, however. Whenever the QWidget::update() method is called, it clears the window, by default, to the background color. So the user sees this sequence:

  1. Window contents

  2. Blank window

  3. Window contents

You almost couldn't design a flicker effect any better. If you are using double-buffering, you are going to overwrite the entire window in one operation (the bitblt()), so there is no need to clear the window first. You can prevent Qt from clearing the window by calling


   1 
   2 setBackgroundMode (NoBackground);
   3 

as shown in line 21.