Previous Book Contents Book Index Next

Inside Macintosh: Apple Game Sprockets Guide /
Chapter 2 - DrawSprocket


Using DrawSprocket

To use DrawSprocket in a game, you need to find the context that best matches the display configuration requested by the game and then reserve and activate the context. When activating the context, you should use the gamma fade capabilities to avoid flicker. During game play you can designate alternate buffers as underlays or overlays to avoid redrawing repeated graphic elements. Finally, you will probably want to take advantage of DrawSprocket's hardware or software page flipping. This section gives code examples of these tasks, as well as an example of cleaning up before quitting and using DrawSprocket's debug mode.

Choosing a Context

One of your game's first tasks is to find the best available context. You can use the DSpFindBestContext function to have DrawSprocket find a context that matches the attributes the game requests.

When you call DSpFindBestContext, passing in a DSpContextAttributes structure, DrawSprocket interprets the attributes specified in the attributes structure as requirements. This insures that the game can search for only those contexts that meet a specific criteria, and know that any matches found will meet those specifications. This is in contrast to what happens when you call DSpContext_Reserve, where the specified attributes are interpreted as requests. This makes it easy for the game to request a feature at reservation time, knowing that the context will be reserved even if that feature is not available.

For example, if the kDSpContextOption_PageFlip bit is set in the attributes structure's contextOptions field when you call DSpFindBestContext, only contexts with hardware page flipping will be considered in the search. However, when you call DSpContext_Reserve to reserve the context and the contextOptions field has the kDSpContextOption_PageFlip bit set, if page flipping is not available for the context being reserved, DrawSprocket will reserve it anyway and use software buffering.

Initializing the Context Attributes Structure

Before beginning, you should initialize the DSpContextAttributes structure you will pass to DSpFindBestContext so that the fields contain no garbage that could cause DrawSprocket to return an error. This is especially important for fields that DrawSprocket can't check, such as the colorTable field. If there is a bad value in the colorTable field when a call to reserve a context is made, DrawSprocket will try to use that color table (causing unpleasant visits to the debugger). The context attributes structure is described on (page 2-27).

Listing 2-1 Initializing a context attributes structure

void MyInitAttributes (DSpContextAttributes *inAttributes)
{
   if (NULL == inAttributes)
      DebugStr("\pStimpy! You Idiot!");
   
   inAttributes->frequency = 0;
   inAttributes->displayWidth = 0;
   inAttributes->displayHeight = 0;
   inAttributes->reserved1 = 0;
   inAttributes->reserved2 = 0;
   inAttributes->colorNeeds = 0;
   inAttributes->colorTable = NULL;
   inAttributes->contextOptions = 0;
   inAttributes->backBufferDepthMask = 0;
   inAttributes->displayDepthMask = 0;
   inAttributes->backBufferBestDepth = 0;
   inAttributes->displayBestDepth = 0;
   inAttributes->pageCount = 0;
   inAttributes->gameMustConfirmSwitch = false;
   inAttributes->reserved3[0] = 0;
   inAttributes->reserved3[1] = 0;
   inAttributes->reserved3[2] = 0;
   inAttributes->reserved3[3] = 0;
}

Finding a Context

This section shows how to find a context that matches attributes required by the game. Listing 2-2 shows how to use DSpFindBestContext (page 2-31) to tell DrawSprocket to find a 320x200 color display with a pixel depth of 8.

Listing 2-2 Finding the best context

DSpContextAttributes theDesiredAttributes;
DSpContextReference theContext;
OSStatus theError;

MyInitAttributes(&theDesiredAttributes);
theDesiredAttributes.displayWidth = 320;
theDesiredAttributes.displayHeight = 200;
theDesiredAttributes.colorNeeds = kDSpColorNeeds_Require;
theDesiredAttributes.backBufferDepthMask = kDSpDepthMask_8;
theDesiredAttributes.displayDepthMask = kDSpDepthMask_8;
theDesiredAttributes.backBufferBestDepth = 8;
theDesiredAttributes.displayBestDepth = 8;
theError = DSpFindBestContext(&theDesiredAttributes,
   &theContext);

Reserving and Activating a Context

Once you have found the best context, you reserve it and then activate it. Follow these steps:

  1. Call the DSpContext_Reserve function (page 2-41). Reserving a context does not result in any change to the user's display because the play state of the context is initially inactive. At this point, the user is still interacting with the system in a normal fashion.
  2. Set the play state of your context to active with the DSpContext_SetState function (page 2-43). This causes the display to change to the resolution you have chosen, and causes a blanking window to cover the entire screen, giving your game complete control over the display.

    (Before activating the context, however, you may want to fade the screen display; see "Fading the Display" (page 2-17) for an example.)

Prior to reserving the context be sure to request any features you would like to use that were not requirements passed to the DSpFindBestContext call. For example, if you want page flipping, request

theDesiredAttributes.contextOptions |= kDSpContextOption_PageFlip;
DrawSprocket will use software buffering if hardware page flipping is not available.

You can also request that DrawSprocket use double or triple buffering by setting the kDSpContextOption_TripleBuffer flag. If you want only double buffering, perhaps because of memory constraints, you should turn off the kDSpContextOption_TripleBuffer option bit.

If both the triple-buffering option bit and the page-flipping option bit are set, and hardware page flipping is available but there are only two video pages --not three--DrawSprocket considers page flipping to be a more important option than triple buffering and drops you down to two VRAM pages, maintaining page flipping.

Listing 2-3 Reserving and activating a context

theError = DSpContext_Reserve(theContext, &theDesiredAttributes);

theError = DSpContext_SetState(theContext, kDSpContextState_Active);

Creating Underlays and Overlays

You can allocate alternate buffers to use for underlays and overlays. An underlay is useful in games that need to restore from a static background. An overlay is useful for things such as a cockpit view that is superimposed on the back buffer before it is displayed.

How Underlays Work

To create an underlay you first create an alternate buffer and draw into it. Then you designate the alternate buffer as the current underlay. Listing 2-4 shows how to use the DSpAltBuffer_New function (page 2-60) to create an alternate buffer and the DSpContext_SetUnderlayAltBuffer (page 2-63) to designate the new buffer as the underlay for a context.

Listing 2-4 Creating an underlay

theError = DSpAltBuffer_New(theContext, false, &theUnderlay);

theError = DSpContext_SetUnderlayAltBuffer(theContext, theUnderlay);
Once you establish an underlay, every time you call DSpContext_GetBackBuffer, the invalid rectangles associated with the returned back buffer are used to copy the image data from the underlay into the back buffer (the first time you get the back buffer, the entire buffer is invalid). In a game with sprites overlaid on the background (the underlay), the sprites are erased when the buffer's invalid rectangles are used to copy the underlay data into the back buffer.

You can make changes in the underlay image, but you must invalidate the underlay rectangles that have been modified in order for the data to be updated to each back buffer (otherwise the back buffer will only have its own invalid rectangles restored). To do this call the DSpAltBuffer_InvalRect function (page 2-65).

You can also have several underlays and switch between them with additional calls to DSpContext_SetUnderlayAltBuffer. However, each underlay change forces a full update in the back buffer unless you immediately invalidate the unique portions of the new underlay with a call to DSpAltBuffer_InvalRect. For example, in a scroller game with a constant horizon area (such as a mountain range), after you switched underlays you would invalidate the areas in the new underlay that differed from the previous one to avoid the full back buffer update. See "Improving Performance With Dirty Rectangles" (page 2-20).

How Overlays Work

As with underlays, you first create a buffer and draw into it. Listing 2-5 shows how to allocate an alternate buffer to use as an overlay using DSpContext_SetOverlayAltBuffer (page 2-62).

Listing 2-5 Creating an overlay

theError = pAltBuffer_New(theContext, false, &theOverlay);

theError = DSpContext_SetOverlayAltBuffer(theContext, theOverlay);
To create the transparency mask that allows the back buffer to show through, DrawSprocket provides the DSpAltBuffer_RebuildTransparencyMask function (page 2-66). Building the transparency mask is a CPU intensive operation and shouldn't be done during game play. Build or rebuild your masks at game startup or at a point in the game where a pause is acceptable (such as between levels).

As with underlays, you can change an overlay or designate a different buffer to serve as the overlay during the game's execution. For example, if you have a cockpit image that changes during game play, as when the user looks out the side of a plane, you can set up multiple overlays and switch them between frames.

You should call DSpAltBuffer_InvalRect on the new overlay if only portions of the overlay have changed (such as a bullet hole appearing in a window); otherwise, the entire buffer is updated. See "Improving Performance With Dirty Rectangles" (page 2-20).

Fading the Display

When you first make your context's play state active, and then subsequently deactivate it when quitting, that is likely to cause a resolution change and an accompanying annoying flicker or flash. Therefore, you will probably want to fade your display out before the change occurs, and then fade it back in afterward.

The gamma fading supported by DrawSprocket is sophisticated and versatile; you may want to use it at other times as well as during resolution changes. See "Gamma Fading" (page 2-9) for more information.

Listing 2-6 shows how to use DSpContext_FadeGammaOut to automatically fade out all displays to black, put a context in the active state with DSpContext_SetState, and then fade back in using DSpContext_FadeGammaIn. You must have at least one reserved context to automatically fade in or out or you will get an error.

The automatic gamma fade functions allow you to specify a zero-intensity color other than black and to fade only specified displays. For details see the DSpContext_FadeGammaOut function (page 2-47) and the DSpContext_FadeGammaIn function (page 2-48). DrawSprocket also provides another function for manual fading--you can fade partway in or out at a speed you choose. See the DSpContext_FadeGamma function (page 2-45).

Listing 2-6 Automatically fading the display

/* fade out all displays */
theError = DSpContext_FadeGammaOut(NULL, NULL);

/* put the context into the active state */
theError = DSpContext_SetState(theContext, kDSpContextState_Active);

/* fade back in */
theError = DSpContext_FadeGammaIn(NULL, NULL);

Double-Buffered Drawing

If you want to use double-buffering (or hardware-supported page flipping) for your drawing, DrawSprocket facilitates easy access to your offscreen buffer. Here are the steps required for double-buffered drawing:

  1. For each frame you draw, call the DSpContext_GetBackBuffer function to obtain a pointer to the graphics port you render to.
  2. Draw the portions of the frame that need updating.
  3. Call the DSpContext_InvalBackBufferRect function for each rectangular area that you have invalidated. This step can greatly enhance performance (see "Improving Performance With Dirty Rectangles" (page 2-20).
  4. Call the DSpContext_SwapBuffers function. At the next vertical retrace DrawSprocket copies the invalidated portions of the back buffer to the device's display.

Listing 2-7 shows how to get the back buffer and set it up as the current port, ready to draw into.

Listing 2-7 Getting the back buffer

/* get the back buffer */
theError = DSpContext_GetBackBuffer(theContext, kDSpBufferKind_Normal,
   &theBackBuffer);

/* set the back buffer to be the current port */
SetPort((GrafPtr)theBackBuffer);
The code in Listing 2-8 draws a rectangle into the back buffer. If you are using an underlay, there is no need to erase the back buffer before drawing; the buffer is automatically filled with the underlay image.

Listing 2-8 Drawing into the buffer

if (!inUseUnderlay)
   EraseRect(&theBackBuffer->portRect);

/* fill the display with a rectangle */
RGBForeColor(&theColor);
PaintRect(&theRectangleRect);
After invalidating the changed parts of the buffer, you are ready to draw the back buffer to the display by calling DSpContext_SwapBuffers, as shown in Listing 2-9.

Listing 2-9 Swapping buffers

theError = DSpContext_SwapBuffers(theContext, NULL, 0);

Improving Performance With Dirty Rectangles

Using dirty rectangles is crucial to achieving optimum performance for your game. A dirty rectangle is an area of the back buffer that has been invalidated so that DrawSprocket knows it has been changed. When you draw into a back buffer, always call DSpContext_InvalBackBufferRect to let DrawSprocket know the bounding rectangle of the area that has been altered. If you do not invalidate the rectangles you use, the entire buffer must be transferred at the next swap.

Invalidating rectangles is most important with software buffering, but it also affects performance with page flipping if you are using underlays or overlays in your game. In any event, you won't pay a performance penalty for telling DrawSprocket about the invalid rectangles--you will only pay a penalty for not doing so.

DrawSprocket provides a separate call for invalidating rectangles in the buffer designated as the current underlay or overlay. If you change pixel data for a current underlay or overlay, you must call DSpAltBuffer_InvalRect in order for the change to be brought into the back buffer. Furthermore, if you change an overlay in such a way that the transparency mask changes, after invalidating the rectangle where the change occurred, you must call DSpAltBuffer_RebuildTransparencyMask in order to re-create the mask. This is a slow process and it is not advisable to do it at runtime.

A "dirty grid" overlays the back buffer and alternate buffers, dividing them into tiles. When you invalidate areas of a buffer, each grid tile that overlaps the dirty rectangle is marked as dirty, even if only a small part of the rectangle intersects with the grid tile. In other words, each grid tile is either all valid or all dirty. The size of the grid is determined in part by the hardware your game is running on and in part by the game itself. DrawSprocket provides functions for adjusting the size of the dirty grid: the DSpContext_SetDirtyRectGridSize function (page 2-53), the DSpContext_GetDirtyRectGridSize function (page 2-54), and theDSpContext_GetDrityRectGridUnits function (page 2-55).

Cleaning Up Before Quitting DrawSprocket

Listing 2-10 illustrates the typical tasks your game must perform before quitting DrawSprocket:

Listing 2-10 Cleaning up

/* fade to black */
theError = DSpContext_FadeGammaOut(NULL, NULL);

/* remove the underlay and release it */
if (inUseUnderlay)
{
   DSpContext_SetUnderlayAltBuffer(theContext, NULL);
   DSpAltBuffer_Dispose(theUnderlay);
   theUnderlay = NULL;
}

/* remove the overlay and release it */
if (inUseOverlay)
{
   DSpContext_SetOverlayAltBuffer(theContext, NULL);
   DSpAltBuffer_Dispose(theOverlay);
   theOverlay = NULL;
}

/* put the context into the inactive state */
theError = DSpContext_SetState(theContext, kDSpContextState_Inactive);

/* fade back in */
theError = DSpContext_FadeGammaIn(NULL, NULL);

/* release the context */
theError = DSpContext_Release(theContext);

Debugging

If you are in a debug cycle, you may want to enable debugging mode in DrawSprocket so that a fade out, followed by a break in the debugger, won't leave you with nothing to see. Listing 2-11 shows how to enable debug mode. You must call DSpSetDebugMode before activating the context in order for the call to take effect. For more information see the DSpSetDebugMode function (page 2-73).

Listing 2-11 Enabling debug mode

DSpSetDebugMode(true);
You can also cause DrawSprocket to enter debug mode by creating a folder in the same folder as your game and naming it "DSpSetDebugMode". This method is handy if you don't want to rebuild your game with the call just to debug it.



Previous Book Contents Book Index Next

© Apple Computer, Inc.
2 JUL 1996