TweetFollow Us on Twitter

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 are Inside 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 develop article.

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;
You don't need to understand all of the fields in the viewPortBufferRecord data structure to use the library. However, if you start having problems getting things to work inside your application and find that you need to modify the screen buffering library, see "The viewPortBufferRecord Data Structure" for some additional helpful information.


THE VIEWPORTBUFFERRECORD DATA STRUCTURE

The following is an accounting of all of the fields of the viewPortBufferRecord data structure.

  • group, device, view -- These are the three elements of an offscreen drawing environment in QuickDraw GX. We need one of each to draw offscreen.
  • buffer, bits, storage -- These objects represent the bits of the offscreen device in decreasing order of abstraction. The field buffer is a bitmap shape that represents the "screen" of the view device. The field bits parallels the contents of buffer and is used to keep information about the offscreen bitmap around between invocations of DrawShapeBuffered, the routine that draws the buffered shape. Finally, the storage field is the handle in Temp Mem (the MultiFinder temporary memory heap) that contains the offscreen data.
  • offxform, on_xform -- These are QuickDraw GX transform objects. offxform has a view port list that contains just the view port for the offscreen device. on_xform has a view port list for the parent of the view port that's being buffered. You may expect that the view port list would be the view port being buffered and not its parent, but when drawing onscreen we've already taken into account all of the transformation and clips of the view port we're buffering, and we just need to copy the end result to the screen. This is why we use the view port's parent and not the view port proper.
  • eraser, marker -- These are the auxiliary shapes used to erase the offscreen buffer and to draw the incoming shapes. The shape eraser is of type gxFullType (a "full" shape) and is the color of the background. The marker (so named to complement eraser) is a picture shape that will be used to draw into the offscreen buffer. The reason for using the marker rather than swapping in the transform of the incoming shape to be offxform (thereby causing the shape to draw offscreen) is that the swapping operation would invalidate the caches for the incoming shape. Instead we use the property of picture shapes of redirecting any drawing to their view port list instead of the shape's own in order to cause incoming shapes to render offscreen. Furthermore, if the incoming shape is the same for every invocation of DrawShapeBuffered, we can test for it and not change the contents of the marker.
  • updatearea -- This is a rectangle shape used to compute the size of the offscreen buffer that is to be generated and what devices it falls on.
  • usehalftone -- This is a Boolean indicating whether to use a halftone in the offscreen buffer.
  • window, parent, screenview, page -- These fields hold incoming parameters to the library. The window field is the window in which drawing will occur. The parent field is a cache for the parent of the view port being buffered (see page 7-18 of Inside Macintosh: QuickDraw GX Objects to learn more about view port hierarchies). The screenview field indicates the view port that will be buffered. The page field is a reference to the last shape passed to DrawShapeBuffered.
  • bounds -- This field indicates the visible area of the screenview in the coordinate space of that view port.
  • invmap -- This is a mapping for translating between the coordinate system of the shapes being drawn in the window and the space of the window itself. If your view is zoomed in at 2x magnification, this mapping will be at 1/2 scale.
  • viewdelta -- This is the position of the upper left corner of the area being buffered, in the local coordinate system of the window. This parameter is used to adjust the drawing in the offscreen buffer so that only the correct part of the shape being buffered is drawn, and to position the content of the offscreen buffer when it's being transferred onto the screen.

In general terms, the code works by allocating a number of QuickDraw GX objects and reusing them as required. Memory for the offscreen buffer is allocated from the MultiFinder temporary memory heap (Temp Mem). Allocation of the storage block is postponed until the last possible moment, and the block is kept locked and nonpurgeable only during the drawing operation. That is, after the resulting image has been transferred to the screen, the block is unlocked and marked purgeable but not disposed of. This permits the same block to be reused in case the memory for the buffer isn't purged.

While most users will keep their windows entirely within the bounds of one screen, it's important to handle the case where a window spans more than one device. Each time the DrawShapeBuffered routine is called (as described later), the code walks the device list checking to see if the area that needs to be buffered intersects a given screen. If it does, the code creates a buffer with the right settings and draws into that device. The process is repeated for each screen.

CREATING AN OFFSCREEN BUFFER

You'll need one view port buffer for each window in your program. To create a view port buffer, use the NewViewPortWBuffer routine.
viewPortBuffer NewViewPortWBuffer(WindowPtr window,
    gxViewPort view, const gxColor *backgroundColor);
Look at the Initialize routine in the file "flicker free.c" for an example of how to use NewViewPortWBuffer. Here's a description of the parameters:

window
The window that the buffering code should draw into.

view
The view port created by your application to draw into the given window. Note that this is different from the object obtained by calling GXNewWindowViewPort, in that this view port should have the window view port set to be its parent.

backgroundColor
A pointer to a gxColor data structure indicating which color should be drawn to erase the offscreen buffer. Passing nil is equivalent to specifying white as the background color.

Let's look at what it takes to create an offscreen buffer in the NewViewPortWBuffer routine (Listing 1). In QuickDraw GX, the place where drawing occurs (for example, the screen or an offscreen buffer) is described by a view device, so the primary purpose of the routine is to create a view device and store it in the device field of the viewPortBufferRecord data structure. Because we want the offscreen device that we specify to be as close as possible to the one into which we will eventually be drawing, you might think that we would go ahead and set all of the attributes of the view device now. But in fact, all that we want to concern ourselves with right now is allocating the gxViewDevice object. Later, when we get to the drawing part, we'll check the screen and our offscreen device and update the gxViewDevice object accordingly.

Listing 1. NewViewPortWBuffer

viewPortBuffer NewViewPortWBuffer(WindowPtr window, gxViewPort view, 
        const gxColor *backgroundColor)
{
    Handle  sbHdl;
    
    if (sbHdl = NewHandleClear(sizeof(viewPortBufferRecord))) {
        gxInk                       background;
        gxHalftone                  halftone;
        viewPortBufferRecord        *sbPtr;
        
        HLock(sbHdl); 
        sbPtr = * (viewPortBufferRecord **) sbHdl;
        sbPtr->window = window;
        sbPtr->screenview = view;
        sbPtr->parent = GXGetViewPortParent(view);
        
        /* We don't allocate storage until we need it. */
        sbPtr->storage = nil;    
        sbPtr->buffer = GXNewShape(gxBitmapType);
        sbPtr->group = GXNewViewGroup();
        sbPtr->view = GXNewViewPort(sbPtr->group);
        sbPtr->device = GXNewViewDevice(sbPtr->group,
                           sbPtr->buffer);
        if (sbPtr->usehalftone =
               GXGetViewPortHalftone(view, &halftone))
            GXSetViewPortHalftone(sbPtr->view, &halftone);
        sbPtr->offxform = GXNewTransform();
        GXSetTransformViewPorts(sbPtr->offxform, 1,
            &sbPtr->view);
        sbPtr->on_xform = GXNewTransform();
        GXSetTransformViewPorts(sbPtr->on_xform, 1,
            &sbPtr->parent);

        background = GXNewInk(); 
        if (backgroundColor) 
            GXSetInkColor(background, backgroundColor);
        else {
            gxColor backcolor;

            backcolor.space = gxRGBSpace;
            backcolor.profile = nil;
            backcolor.element.rgb.red = 
                backcolor.element.rgb.green = 
                backcolor.element.rgb.blue = 0xFFFF;
            GXSetInkColor(background, &backcolor);
        }
        sbPtr->eraser = GXNewShape(gxFullType);
        GXSetShapeInk(sbPtr->eraser, background);
        GXDisposeInk(background);

        /* The initial bounds for the offscreen is the entire */
        /* window. */
        sbPtr->bounds.left = ff(window->portRect.left);
        sbPtr->bounds.top = ff(window->portRect.top);
        sbPtr->bounds.right = ff(window->portRect.right);
        sbPtr->bounds.bottom = ff(window->portRect.bottom);
        sbPtr->updatearea = GXNewRectangle(&sbPtr->bounds);
        GXSetShapeViewPorts(sbPtr->updatearea, 1, &sbPtr->parent);
        sbPtr->marker = GXNewShape(gxPictureType);
        GXSetShapeTransform(sbPtr->eraser, sbPtr->offxform);
        GXSetShapeTransform(sbPtr->marker, sbPtr->offxform);
        GXSetShapeTransform(sbPtr->buffer, sbPtr->on_xform);
        ResetMapping(&sbPtr->invmap);

        /* The rest of the fields in the block are initialized to */
        /* 0 by the "Clear" in the NewHandleClear used to allocate */
        /* this block. */

        HUnlock(sbHdl);
    }
    return ((viewPortBuffer) sbHdl);
}
To create a view device we need a view group and a bitmap. Eventually we'll want to fill in all of the values of the gxBitmap object to match the screen, but for now the default values assigned to the bitmap by calling GXNewShape are sufficient.

The NewViewPortWBuffer routine also allocates a number of auxiliary objects that will be needed during the operation of the offscreen buffer. These include the following:

  • a gxShape object to be used to erase the offscreen buffer
  • a pair of gxTransform objects to direct drawing of incoming shapes to the offscreen buffer and of the content of the offscreen buffer to the screen
Because we'll use these objects throughout the life of the offscreen buffer, we'll do best by allocating them now and releasing them at the end. Whenever possible, you'll want to allocate objects that you'll use throughout the life of your application up front, work with them by changing their attributes, and dispose of them at the end.

DISPOSING OF THE BUFFER

When you've finished using the window and want to deallocate the memory being used by the view port buffer, you should call DisposeViewPortWBuffer.
void DisposeViewPortWBuffer(viewPortBuffer sb);
sb The object previously returned from NewViewPortWBuffer.
As shown in Listing 2, DisposeViewPortWBuffer just runs through the viewPortBufferRecord data structure and disposes of all of the objects allocated by NewViewPortWBuffer.

Listing 2. DisposeViewPortWBuffer

void DisposeViewPortWBuffer(viewPortBuffer sb)
{
    viewPortBufferRecord        *sbPtr;
    
    HLock((Handle) sb); 
    sbPtr = *sb;

    /* We need to dispose of all of the things that we allocated. */
    GXDisposeShape(sbPtr->marker);
    GXDisposeShape(sbPtr->eraser);
    GXDisposeTransform(sbPtr->on_xform);
    GXDisposeTransform(sbPtr->offxform);
    GXDisposeViewDevice(sbPtr->device);
    GXDisposeViewPort(sbPtr->view);
    GXDisposeViewGroup(sbPtr->group);
    GXDisposeShape(sbPtr->buffer);
    if (sbPtr->storage) DisposeHandle(sbPtr->storage);
    
    HUnlock((Handle) sb);
    DisposeHandle((Handle) sb);
}

UPDATING THE BUFFER

When some aspect of the window in which you're drawing changes, you need to call UpdateViewPortWBuffer. In particular, if you change the clip shape or the mapping of the viewPort object that you originally passed to NewViewPortWBuffer, you need to call UpdateViewPortWBuffer. Typically, you'll need to change the clip shape of the view port to keep QuickDraw GX from drawing shapes over the scroll bar area, and you'll need to change the mapping in order to zoom in or scroll.
void UpdateViewPortWBuffer(viewPortBuffer sb, gxShape clip, 
                            gxMapping *displaymap);
sb The object previously returned from NewViewPortWBuffer.
clip The clip shape that should be applied when drawing into the window previously passed to NewViewPortWBuffer. Passing nil leaves the current clip shape untouched. The initial setting is for drawing to occur in the entire contents of the window (including the area typically assigned to scroll bars).
displaymap The parameter used to update the view port buffer if you change the mapping of your window view port in order to zoom in or scroll. If nil, the current mapping is left untouched. The initial setting is the identity mapping.

DRAWING ON THE SCREEN

Now we get to the real substance of the library -- the buffering routine and its supporting code.

When you want to draw on the screen, you'll call DrawShapeBuffered instead of GXDrawShape. If the memory is available to double buffer your drawing, DrawShapeBuffered will result in a flicker-free update; otherwise the routine will simply call GXDrawShape.

void DrawShapeBuffered(viewPortBuffer sb, gxShape page, 
        const gxRectangle *updatebounds);
sb The object previously returned from NewViewPortWBuffer.
page The shape that you want to draw inside the window. This is typically a QuickDraw GX picture shape into which all of the shapes that make up a document have been collected.
updatebounds A pointer to a QuickDraw GX rectangle indicating what area of the document is to be updated. The location of the rectangle is given in the coordinate system of the window's portRect. If nil, the code draws the area inside the clip shape passed to UpdateViewPortWBuffer.
As shown in Listing 3, the first thing that the buffering routine does is to compute the global bounds of the view port that's being buffered. Optionally, you could specify what area inside the view port you want to have buffered. Otherwise the routine attempts to draw all of the view port that's visible on the screen.

Listing 3. DrawShapeBuffered

void DrawShapeBuffered(viewPortBuffer sb, gxShape page, 
        const gxRectangle *updatebounds)
{
    viewPortBufferRecord        *sbPtr;
    gxRectangle                 bounds;
    
    HLock((Handle) sb); 
    sbPtr = *sb;
        
    if (updatebounds) {
        gxMapping   map;
            
        GXGetViewPortMapping(sbPtr->screenview, &map);
        bounds = *updatebounds;
        bounds.left = bounds.left & 0xFFFF0000;
        bounds.right = (bounds.right + 0xFFFF) & 0xFFFF0000;
        bounds.top = bounds.top & 0xFFFF0000;
        bounds.bottom = (bounds.bottom + 0xFFFF) & 0xFFFF0000;
        MapPoints(&map, 2, (gxPoint *) &bounds);
        bounds.left = bounds.left & 0xFFFF0000;
        bounds.right = (bounds.right + 0xFFFF) & 0xFFFF0000;
        bounds.top = bounds.top & 0xFFFF0000;
        bounds.bottom = (bounds.bottom + 0xFFFF) & 0xFFFF0000;

        /* We remove the fractional part BEFORE the call to */
        /* MapPoints because we're rounding to enclose all pixels */
        /* intersected by the rectangle. Pixels are integers. */
        /* Coordinates are fractional. */
    } 
    else
        bounds = sbPtr->bounds;
    /* The above given bounds is in the window space - just right. */
    GXSetRectangle(sbPtr->updatearea, &bounds);
    /* Check to see that the shape is actually visible on the */
    /* screen and then proceed to draw. */
    if (bounds.left < bounds.right && bounds.top
            < bounds.bottom) {
        GDHandle        screen;
        
        if (sbPtr->page != page) {
            GXSetPicture(sbPtr->marker, 1, &page, nil, nil, nil);
            sbPtr->page = page;
        }

        if (screen = GetDeviceList()) {
            do {
                gxViewDevice device = GXGetGDeviceViewDevice(screen);

                /* Note that we reuse the bounds in here. */
                if (GXGetShapeDeviceBounds(sbPtr->updatearea,
                        sbPtr->parent, device, &bounds))
                    BufferDrawing(sbPtr, &bounds, device);
            } while (screen = GetNextDevice(screen));
        }
    }
}
If you haven't caught on to the fact that you can connect multiple screens to your Macintosh, the last part may be a little confusing. Once the routine has figured the global bounds of the visible part of the view port that it's buffering, it walks the device list checking to see if those bounds intersect each of the devices connected to the CPU and then calls the routine that performs the drawing (BufferDrawing, shown in Listing 4). Since most of the time a window will be completely contained within one screen, the BufferDrawing routine will be called only once per invocation of DrawShapeBuffered. The nice thing about breaking up the code this way is that the BufferDrawing routine can assume that it's drawing to a single device and therefore it's safe to make assumptions about the device's capabilities.

Listing 4. BufferDrawing

static void BufferDrawing(viewPortBufferRecord *sbPtr, 
        const gxRectangle *boundsPtr, gxViewDevice target)
{
    gxRectangle     bounds = *boundsPtr;
    long                depth, size, gxstatus;
    gxMapping       map, savemap;
    gxShape         devsh;
    gxBitmap            devbits;
    OSErr               status; 
    gxPoint         viewloc;
    gxBitmap            oldbits = sbPtr->bits;

    /* Fill in all the values of sbPtr->bits. */
    ...

    viewloc.x = bounds.left;    /* These numbers are already in */
    viewloc.y = bounds.top;     /* local space. */

    /* Compute the onscreen location of the buffer. */
    ...

    /* This is the important part, allocating the actual bits. */
    size = sbPtr->bits.rowBytes * sbPtr->bits.height;
    check(size > 0);
    if (sbPtr->storage) {
        if ((* (sbPtr->storage)) != nil)
            SetHandleSize(sbPtr->storage, size);
        else {
            ReallocHandle(sbPtr->storage, size);
            nrequire(status = MemError(), TempBufferFailed);
        }
    }
    else
        require(sbPtr->storage = TempNewHandle(size, &status), 
                        TempBufferFailed);
    HNoPurge(sbPtr->storage);
    HLock(sbPtr->storage);
    sbPtr->bits.image = * ((void **) sbPtr->storage);
    
    /* See if we need to invalidate all of the world when we do */
    /* this. */
    if (oldbits.image != sbPtr->bits.image ||
         oldbits.width != sbPtr->bits.width ||
         oldbits.height != sbPtr->bits.height ||
         oldbits.rowBytes != sbPtr->bits.rowBytes ||
         oldbits.pixelSize != sbPtr->bits.pixelSize ||
         oldbits.space != sbPtr->bits.space ||
         (oldbits.set != sbPtr->bits.set && oldbits.set && 
            GXEqualColorSet(oldbits.set, sbPtr->bits.set) == false) ||
         (oldbits.profile != sbPtr->bits.profile && 
            oldbits.profile &&
            GXEqualColorProfile(oldbits.profile, sbPtr->bits.profile) 
                                == false)) {
        GXSetBitmap(sbPtr->buffer, &sbPtr->bits, nil);
        GXSetViewDeviceBitmap(sbPtr->device, sbPtr->buffer);
    } 
    else {                          /* We test this one instead */
        sbPtr->bits.set = oldbits.set;  /* of the disposed one. */
        sbPtr->bits.profile = oldbits.profile;      /* Ditto */
    }
    
    /* Erase the offscreen bitmap, draw the shape into it, and */
    /* then copy it onscreen. */
    GXDrawShape(sbPtr->eraser);      /* Erase. */
    GXDrawShape(sbPtr->marker);      /* Buffer. */
    GXDrawShape(sbPtr->buffer);      /* Transfer -- done. */
    HUnlock(sbPtr->storage);
    HPurge(sbPtr->storage);
    if (devsh) 
        GXDisposeShape(devsh);    /* Dispose of the device bitmap. */
    GXGetGraphicsError(&gxstatus);
    ncheck(gxstatus);
    if (gxstatus) 
        goto DrawingFailed;
    return;
    
TempBufferFailed:   
    GXDisposeShape(devsh);        /* Dispose of the device bitmap. */

DrawingFailed:
    GXDrawShape(sbPtr->updatearea);
    GXDrawShape(sbPtr->page);
}
This approach of walking the device list is preferred to maintaining a buffer for each screen and having a routine to update the buffer list every time a window is moved. The latter approach would result in only minor performance improvements, and only when the window intersected more than one device. Since this is a rare case, the additional housekeeping isn't worth the trouble.

The key to understanding DrawShapeBuffered is the equivalence between the QuickDraw data type GDHandle and a QuickDraw GX view device. To walk the device list, the code uses the QuickDraw routines GetDeviceList and GetNextDevice. The GXGetShapeDeviceBounds routine converts a GDHandle to a view device. From the view device we can find out which area of the screen intersects the area that we're being asked to update.

    The Display Manager can help you walk the device list, as discussed in the Graphical Truffles column in this issue of develop.*

In BufferDrawing, all of the parameters needed to create an offscreen bitmap as required by the given device are finally computed. Note that in the BufferDrawing routine there are no calls that create new objects; there are only calls that modify objects that were created when NewViewPortWBuffer was called. The modifications are done only if needed. For example, before calling GXSetTransformMapping, the library checks to see if the mapping has changed and merits updating. Without this check, the transform cache would be needlessly invalidated some of the time. Similarly, the code checks to see if any of the parameters of the bitmap for the offscreen view device have changed before calling GXSetBitmap and GXSetViewDeviceBitmap.

Changing the bitmap for the view device is one of the most expensive operations in QuickDraw GX because it invalidates most of the drawing caches. Fortunately, the check to see if the bitmap needs to be updated executes very quickly in spite of its length, and the cost of rebuilding all of the shape caches is avoided if possible.

The most confusing thing in the BufferDrawing routine is the call to the GXGetDeviceBitmap routine (omitted from Listing 4; see the full code on the CD for details) and the subsequent call to GXDisposeShape for the same object. This routine obtains a copy of the read-only object in QuickDraw GX that represents the bitmap for a given screen. There are two important points about this. The first is that since we're being given a copy and not the object itself, we have to dispose of the object after we're finished with it. You may think that it would be more efficient to get the object during the initialization routine and then dispose of it when we're all done. But that's the other important point. Since the object that we have is a copy of the original, our copy would not be updated if the depth of the monitor was changed or the color table for the device had been updated. As a result of these two points, we're forced to allocate an object every time through our drawing loop, something that should be avoided in general.

THE REST OF THE ROUTINES

The rest of the routines in the offscreen buffering library provide support and access to some of the internal fields of the viewPortBufferRecord data structure. If you need more information on how to use these, look at the sample code included on this issue's CD.

I'll mention one other routine here. Because the internal view port created by the library is inaccessible from the outside, the routine SetViewPortWBufferDither is provided to change the dither level of the view port. If you need to change other attributes of the offscreen view port, use the SetViewPortWBufferDither routine as a template.

void SetViewPortWBufferDither(viewPortBuffer sb,
        const long ditherlevel);
sbThe object previously returned from NewViewPortWBuffer.
ditherlevelThe dithering level to set the offscreen view port to.

THINGS TO TRY IN THE CODE

If the code presented so far doesn't meet your particular needs, you may want to try changing or fine tuning it. Here are some suggestions and observations about things that you may want to try.

BITMAP ALLOCATION

Currently the code looks for memory in the MultiFinder temporary memory heap (Temp Mem) and will back down in case it can't obtain the memory for the offscreen buffer. If you need to guarantee that your drawing will be screen buffered, you'll need to change the memory allocation code inside the BufferDrawing routine.

INTEGRATED ERROR HANDLING

There are two places where memory allocation can trip the screen buffering library. If the library fails to allocate enough memory to hold its data structures, it will return nil from NewViewPortWBuffer. You may need to change this to better fit in with your application's error model.

The library will handle a failure to allocate the offscreen bitmap by resorting to drawing with GXDrawShape. If you want something different, see "Bitmap Allocation" above.

DEEP POCKETS

If the original data that you'll be working with requires more bits than are on the display that you're running on, you may want to create an offscreen buffer that's deeper than the screen and then take advantage of the dithering or halftoning mechanisms in QuickDraw GX to allow user manipulation. The code that checks for changes in the screen view port's halftone should give you a good idea of how to do that.

STEADY, NOW

Now you understand how to use double buffering to prevent flicker in your QuickDraw GX application. You may need to do some fine tuning of the screen buffering library to fit your purposes, but the result will be worth it. Users will appreciate the more professional look of your application and their eyes won't tire as quickly as they peer at a flicker-free screen.


    RELATED READING

    • "Getting Started With QuickDraw GX" by Pete "Luke" Alexander, develop Issue 15.
    • Inside Macintosh: QuickDraw GX Objects and Inside Macintosh: QuickDraw GX Graphics (Addison-Wesley, 1994).

    HUGO M. AYALA (hugo@mit.edu, http://web.mit.edu/hugo/www) spent five years working on QuickDraw GX as a development engineer at Apple before returning to MIT to pursue a Ph.D. in mechanical engineering. His current research interest is how to design the undercarriage of large earth-moving equipment so that it doesn't get thrashed so fast by rocks and dirt. To pay for the Ph.D., he moonlights doing computer graphics work, which has been his hobby since he was a lad. After finishing his Ph.D., Hugo plans to branch off into drawing comic strips, like the one that he's been drawing for his school newspaper. If you ever try to give Hugo directions, you need to know that he's directionally challenged -- he really can't tell his left from his right.*

    Thanks to our technical reviewers Dave Bice, Brian Chrisman, Tom Dowdy, David Hayward, and Ingrid Kelly.*

 
AAPL
$97.89
Apple Inc.
+0.22
MSFT
$44.03
Microsoft Corpora
-0.47
GOOG
$585.34
Google Inc.
-3.68

MacTech Search:
Community Search:

Software Updates via MacUpdate

Bartender 1.2.20 - Organize your menu ba...
Bartender lets you organize your menu bar apps. Features: Lets you tidy your menu bar apps how you want. See your menu bar apps when you want. Hide the apps you need to run, but do not need to... Read more
TotalFinder 1.6.2 - Adds tabs, hotkeys,...
TotalFinder is a universally acclaimed navigational companion for your Mac. Enhance your Mac's Finder with features so smart and convenient, you won't believe you ever lived without them. Tab-based... Read more
Vienna 3.0.0 RC 2 :be5265e: - RSS and At...
Vienna is a freeware and Open-Source RSS/Atom newsreader with article storage and management via a SQLite database, written in Objective-C and Cocoa, for the OS X operating system. It provides... Read more
VLC Media Player 2.1.5 - Popular multime...
VLC Media Player is a highly portable multimedia player for various audio and video formats (MPEG-1, MPEG-2, MPEG-4, DivX, MP3, OGG, ...) as well as DVDs, VCDs, and various streaming protocols. It... Read more
Default Folder X 4.6.7 - Enhances Open a...
Default Folder X attaches a toolbar to the right side of the Open and Save dialogs in any OS X-native application. The toolbar gives you fast access to various folders and commands. You just click... Read more
TinkerTool 5.3 - Expanded preference set...
TinkerTool is an application that gives you access to additional preference settings Apple has built into Mac OS X. This allows to activate hidden features in the operating system and in some of the... Read more
Audio Hijack Pro 2.11.0 - Record and enh...
Audio Hijack Pro drastically changes the way you use audio on your computer, giving you the freedom to listen to audio when you want and how you want. Record and enhance any audio with Audio Hijack... Read more
Intermission 1.1.1 - Pause and rewind li...
Intermission allows you to pause and rewind live audio from any application on your Mac. Intermission will buffer up to 3 hours of audio, allowing users to skip through any assortment of audio... Read more
Autopano Giga 3.6 - Stitch multiple imag...
Autopano Giga allows you to stitch 2, 20, or 2,000 images. Version 3.0 integrates impressive new features that will definitely make you adopt Autopano Pro or Autopano Giga: Choose between 9... Read more
Airfoil 4.8.7 - Send audio from any app...
Airfoil allows you to send any audio to AirPort Express units, Apple TVs, and even other Macs and PCs, all in sync! It's your audio - everywhere. With Airfoil you can take audio from any... Read more

Latest Forum Discussions

See All

Exploration Focused Puzzle Game Beatbudd...
Exploration Focused Puzzle Game Beatbuddy Set to Make Transition from PC to iOS this September Posted by Jennifer Allen on July 28th, 2014 [ permalink ] | Read more »
PlanetHD
PlanetHD By Nadia Oxford on July 28th, 2014 Our Rating: :: SPACE MADNESSUniversal App - Designed for iPhone and iPad PlanetHD will keep players busy for a while, though its unpredictable physics are a handful to deal with.   | Read more »
This Week at 148Apps: July 21-25, 2014
Another Week of Expert App Reviews   At 148Apps, we help you sort through the great ocean of apps to find the ones we think you’ll like and the ones you’ll need. Our top picks become Editor’s Choice, our stamp of approval for apps with that little... | Read more »
Reddme for iPhone - The Reddit Client (...
Reddme for iPhone - The Reddit Client 1.0 Device: iOS iPhone Category: News Price: $.99, Version: 1.0 (iTunes) Description: Reddme for iPhone is an iOS 7-optimized Reddit client that offers a refreshing new way to experience Reddit... | Read more »
Jacob Jones and the Bigfoot Mystery : Ep...
Jacob Jones and the Bigfoot Mystery : Episode 2 1.0 Device: iOS Universal Category: Games Price: $1.99, Version: 1.0 (iTunes) Description: Jacob Jones is back in Episode 2 of one of Apples 'Best of 2013' games and an App Store... | Read more »
New Trailer For Outcast Odyssey, A New K...
New Trailer For Outcast Odyssey, A New Kind of Card Battler Posted by Jennifer Allen on July 25th, 2014 [ permalink ] Out this Fall is a new kind of card battle game: Outcast Odyssey. | Read more »
Hay Day – Tip, Tricks, Strategies, and C...
Recently got into Supercell’s other huge hit, Hay Day and could do with some advice on what to do? We’ve got you covered with some helpful trips and tricks to bear in mind! Ticking Along One of the key things to keep in mind while building up that... | Read more »
Monster Head Review
Monster Head Review By Nadia Oxford on July 25th, 2014 Our Rating: :: FEEDING TIMEUniversal App - Designed for iPhone and iPad Racking up a high score with Monster Head is trickier than it first appears. The appeal wears out fairly... | Read more »
Garfield: Survival of the Fattest Coming...
Garfield: Survival of the Fattest Coming to iOS this Fall Posted by Jennifer Allen on July 25th, 2014 [ permalink ] Who loves lasagna? Me. Also everyone’s favorite grumpy fat cat, Garfield. | Read more »
Happy Flock Review
Happy Flock Review By Andrew Fisher on July 25th, 2014 Our Rating: :: HERD IT ALL BEFOREUniversal App - Designed for iPhone and iPad Underneath the gloss of Happy Flock’s visuals is a game of very little substance. It’s cute, but... | Read more »

Price Scanner via MacPrices.net

13-inch 2.5GHz MacBook Pro on sale for $1099,...
Best Buy has the 13″ 2.5GHz MacBook Pro available for $1099.99 on their online store. Choose free shipping or free instant local store pickup (if available). Their price is $100 off MSRP. Price is... Read more
Roundup of Apple refurbished MacBook Pros, th...
The Apple Store has Apple Certified Refurbished 13″ and 15″ MacBook Pros available for up to $400 off the cost of new models. Apple’s one-year warranty is standard, and shipping is free. Their prices... Read more
Record Mac Shipments In Q2/14 Confound Analys...
A Seeking Alpha Trefis commentary notes that Apple’s fiscal Q3 2014 results released July 22, beat market predictions on earnings, although revenues were slightly lower than anticipated. Apple’s Mac’... Read more
Intel To Launch Core M Silicon For Use In Not...
Digitimes’ Monica Chen and Joseph Tsai, report that Intel will launch 14nm-based Core M series processors specifically for use in fanless notebook/tablet 2-in-1 models in Q4 2014, with many models to... Read more
Apple’s 2014 Back to School promotion: $100 g...
 Apple’s 2014 Back to School promotion includes a free $100 App Store Gift Card with the purchase of any new Mac (Mac mini excluded), or a $50 Gift Card with the purchase of an iPad or iPhone,... Read more
iMacs on sale for $150 off MSRP, $250 off for...
Best Buy has iMacs on sale for up to $160 off MSRP for a limited time. Choose free home shipping or free instant local store pickup (if available). Prices are valid for online orders only, in-store... Read more
Mac minis on sale for $100 off MSRP, starting...
Best Buy has Mac minis on sale for $100 off MSRP. Choose free shipping or free instant local store pickup. Prices are for online orders only, in-store prices may vary: 2.5GHz Mac mini: $499.99 2.3GHz... Read more
Global Tablet Market Grows 11% in Q2/14 Notwi...
Worldwide tablet sales grew 11.0 percent year over year in the second quarter of 2014, with shipments reaching 49.3 million units according to preliminary data from the International Data Corporation... Read more
New iPhone 6 Models to Have Staggered Release...
Digitimes’ Cage Chao and Steve Shen report that according to unnamed sources in Apple’s upstream iPhone supply chain, the new 5.5-inch iPhone will be released several months later than the new 4.7-... Read more
New iOS App Helps People Feel Good About thei...
Mobile shoppers looking for big savings at their favorite stores can turn to the Goodshop app, a new iOS app with the latest coupons and deals at more than 5,000 online stores. In addition to being a... Read more

Jobs Board

*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
WW Sales Program Manager, *Apple* Online St...
**Job Summary** Imagine what you could do here. At Apple , great ideas have a way of becoming great products, services, and customer experiences very quickly. Bring Read more
Lead Software Engineer, *Apple* Online Stor...
**Job Summary** Imagine what you could do here. At Apple , great ideas have a way of becoming great products, services, and customer experiences very quickly. Bring Read more
Manager, *Apple* Fullfillment Operation (AF...
…cross-functional teams to drive the highest level of program management quality for Apple . You will help plan launch strategy and demand generation programs with Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.