March 96 - Flicker-Free Drawing With QuickDraw GX
Flicker-Free Drawing With QuickDraw GX
Hugo Ayala
Does your QuickDraw GX application have a look reminiscent of the old silent movies?
If so, it suffers from flicker. But don't despair -- help is as near as this issue's CD,
where you'll find a ready-to-use library for doing memory-efficient, flicker-free
drawing inside a window. This article explores the problem of flicker and its solutions
and walks you through the code.
My first encounter with the idea of flicker-free drawing happened when I was a
12-year-old kid reading my father's copy of Nibble, a journal about programming the
Apple II. A review of new products mentioned a program that had impeccable animation
and guessed that the programmer was most likely using "page switching" to produce
flicker-free drawing. Page switching (or page flipping) took advantage of the fact that
the Apple II could use more than one location in memory (more than one page) to hold
the screen image. Given enough memory, a programmer could set things up so that
there was a second "offscreen" page to draw into while the first was being shown on the
screen. Switching back and forth between these two pages made flicker-free drawing
possible.
Today's hardware bears little resemblance to the Apple II, but the technique for doing
flicker-free drawing is essentially the same. It involves double buffering (also known
as screen buffering) -- causing all objects to be drawn first into an offscreen buffer
and then copying that entire buffer to the front buffer (the window). Both this and the
Apple II method eliminate the need to erase the old position of a moving image directly
on the screen before drawing its new position, which is the primary cause of flicker.
The library that accompanies this article manages an offscreen buffer for a QuickDraw
GX view port. Using it will enable you to give your QuickDraw GX application a more
professional feel by removing flicker. You could use the offscreen library provided
with QuickDraw GX to do screen buffering, but because it's a much more
general-purpose tool, you would have to handle most of the minutiae of examining
screen devices, filling out the bitmap data structures, and allocating and releasing the
memory yourself. The library provided on this issue's CD does all of that for you.
I'll walk you through the library code, illustrated by the sample application called
Flicker Free on the CD, but first I'll give some background on the problem of flicker
and its solutions. This article assumes that you already know a thing or two about
QuickDraw GX; if you don't, see the article "Getting Started With QuickDraw GX" in
develop Issue 15. The essential references areInside Macintosh: QuickDraw GX Objects
and Inside Macintosh: QuickDraw GX Graphics.
FLICKER -- ITS CAUSES AND SOLUTIONS
For a dramatic illustration of flicker, run the sample application Flicker Free (you'll
need a color Macintosh with QuickDraw GX installed). You'll see a window filled with
fifty circles bouncing around in different directions (see Figure 1).
Figure 1. The startup screen from the sample application Flicker Free
The Drawing menu in the Flicker Free application offers a choice of buffering methods:
full screen buffering, no screen buffering, and half and half. The program starts up in
half-and-half mode: the left side of the window (the side with the Apple menu, for
those like me who can't tell left from right) is being buffered, while the other side
isn't. Switch among the buffering choices to get a sense of the difference that flicker or
its absence makes in how you experience the animation.
What causes flicker? In our case, the shapes on the right are being erased and then
redrawn over and over again as they move across the screen. And although the
rendering of the shapes is very fast (your mileage may vary according to CPU speed),
the act of constantly drawing and erasing them makes the whole thing look like an old
silent movie. In places where circles overlap, pixels are made to take on different
colors as each shape is drawn. In the resulting blur of colors, it's hard to see which
shape is in front.
The key to avoiding flicker is to avoid erasing pixels on the screen needlessly between
two stages of a drawing and to change only the color of those pixels that need to change.
The left side of our sample application window is being double buffered, meaning that
each circle is drawn into an offscreen buffer and then the whole scene is transferred
onto the screen. Because at each step in the animation only the pixels that need to
change color do, the movement of the circles is rendered flicker free. With double
buffering there's no problem telling which circles are in front. Shapes move neatly
past each other.
Figure 2 shows two frame-by-frame drawing sequences illustrating the difference
between an update full of flicker and a flicker-free update.
The upper set of frames in Figure 2 shows what happens without double buffering. The
screen is erased (in frame 2 and then again, out of view, in frame 7) and then each
circle is added to the screen in its new position. The whole assembly of circles appears
on the screen only briefly before they're erased and the process is started again. The
lower set of frames in the figure shows the update process during double buffering.
The offscreen image is transferred to the screen in a sweep replacing the previous
image. You can see the sweep line as a very subtle horizontal break in the frame.
Figure 2. An update full of flicker vs. a flicker-free update
The sample application gives a dramatic demonstration of how flicker affects
animation. But even if your QuickDraw GX application isn't an animation package, it
probably suffers from some form of flicker when update events are serviced. The most
common and most annoying flicker occurs when applications engage in some form of
user interaction -- for example, dragging marquees, manipulating shapes, and editing
text.
BUFFERING TRADEOFFS
When you're considering using screen buffering, it's important to understand the
tradeoff with drawing speed. In the sample application, the speed at which the circles
travel is a function of the number of circles in the window, the size of the window, and
your choice of screen buffering. Given the same window size and number of shapes,
drawing with screen buffering is always slower than with no screen buffering. Screen
buffering involves the same amount of work as screen drawing plus the additional step
of transferring the offscreen image onto the screen.
When the window contains one circle, the unbuffered performance is at least three
times faster than that of the buffered case (again, your mileage may vary depending on
your CPU speed). As more shapes are added, the performance in both cases goes down,
but so does the performance gap between the two: the unbuffered performance doesn't
have as much of an advantage over the buffered performance. This is because the speed
at which the offscreen buffer is transferred to the screen is independent of the
complexity of the shape it contains; it's purely a function of its size. As the complexity
of the shape being buffered increases, the relative cost of shape buffering decreases.
Now, this doesn't mean that you should buffer only complex shapes that take a long
time to draw. What it means is that when you add screen buffering to your application,
you have to be mindful of what constitutes a reasonable tradeoff between buffering and
drawing performance. You should try things out and see if screen buffering is the
technique best suited to your needs. Alternatives to screen buffering that enable
flicker-free drawing include the use of transfer modes and geometric operations. I
hope to discuss these in a future developarticle.
Meanwhile, we'll take a look at the screen buffering library that accompanies this
article, which is ready for you to incorporate into your QuickDraw GX application. I
wrote the library with performance issues in mind. Thus, it takes advantage of the fact
that in the QuickDraw GX graphics object model, information that's needed to render a
shape can be computed once, stored in a drawing cache, and reused every time that
shape is drawn. The library is very careful to check before making calls that
invalidate drawing caches, so the overhead of offscreen drawing is kept to a minimum.
A LOOK AT THE SCREEN BUFFERING LIBRARY
Everything you need in order to use the screen buffering library is defined in the
interface file. The library consists of four major routines: the routine that creates the
view port buffer object, the one that disposes of it, the one that updates it, and the one
that uses it to actually buffer screen drawing. The four corresponding calls should
parallel the drawing loop inside your application.
The include file defines only one data type:
typedef struct viewPortBufferRecord **viewPortBuffer;
The internals of the data type are private to the "screen buffering.c" file and are as
follows:
struct viewPortBufferRecord {
gxViewGroup group; /* The offscreen's view group. */
gxViewDevice device; /* The offscreen's view device. */
gxViewPort view; /* The offscreen's view port. */
gxShape buffer; /* The bitmap of the offscreen's */
/* view device. */
gxBitmap bits; /* Source structure for the */
/* buffer shape. */
Handle storage; /* A temp handle to the bits of */
/* the bitmap. */
gxTransform offxform; /* This draws into the offscreen. */
gxTransform on_xform; /* This draws onscreen. */
gxShape eraser; /* Erases offscreen to background */
/* color. */
gxShape marker; /* Used to draw into the */
/* offscreen. */
gxShape updatearea; /* Represents the area to update. */
short usehalftone; /* True if screen has a halftone. */
WindowPtr window; /* The window of the view port. */
gxViewPort parent; /* The parent's view port. */
gxViewPort screenview; /* The view port to buffer. */
gxShape page; /* The shape that we're asked to */
/* draw. */
gxRectangle bounds; /* The offscreen's bounds. */
gxMapping invmap; /* The inv offscreen view port */
/* map. */
gxPoint viewdelta; /* The last delta for the */
/* offscreen. */
};
typedef struct viewPortBufferRecord viewPortBufferRecord;