Contents

General Design Philosophy

3D Graphics and Sound

Network Communications

3D Graphics

When developing a 3D engine for both Windows and Macintosh, one has little choice but to use OpenGL, primarily because it is the only 3D API supported on the Macintosh platform. OpenGL carries the tremendous advantage that it is a cross-platform standard. Almost all of a game’s 3D code can be written once and be guaranteed to work on both platforms. The only piece that is dependent on the operating system is the initial context setup. Both the Windows and Macintosh operating systems supply a small API to link OpenGL to their internal windowing systems. On Windows, it is called WGL, and on the Macintosh, it is known as AGL. These libraries enable an application to query the pixel formats that the 3D hardware supports and to bind an OpenGL context to a window.

Listing 1 shows the Windows and Macintosh versions of a class named GraphicsContext which encapsulates an OpenGL context. The constructor of this class creates an OpenGL context and associates it with a window. The first step towards accomplishing this is to tell the operating system what pixel format and context attributes are desired. Both WGL and AGL supply a function which takes this information and returns the most appropriate pixel format identifier. With this identifier in hand, an OpenGL context can be created and bound to a window. It is important to note that on both platforms, each thread of execution may have only one active OpenGL context at any time, as specified with the wglMakeCurrent and aglSetCurrentContext functions.

The SetPixelFormat function on the Windows platform carries with it the limitation that it may be called only once for any given device context. This becomes problematic if you would like to change resolutions or pixel depths and thus need to create a new OpenGL context for a window which previously had one bound to it. Destroying and recreating the main window can just make the problem worse since other game systems which are also associated with that window would have to be restarted as well. A nice workaround is to create a full-size child window of the main window and use it as the window to which the OpenGL context is attached. This way, only the child window needs to be destroyed and recreated allowing OpenGL context replacement without disturbing other systems.

Once a context has been created and rendering has occurred, the image is displayed by calling a single WGL or AGL function which swaps the OpenGL drawing buffers. This is demonstrated in the UpdateContext function shown in Listing 1.

Accessing OpenGL extensions is the only remaining area in which Windows and Macintosh methods differ. Once you have determined that an extension is available (by calling glGetString with the GL_EXTENSIONS selector), Windows implementations must call wglGetProcAddress to retrieve a pointer to any function defined by the extension. Extensions currently supported on the Macintosh already have entry points for their functions defined in the OpenGL library, so there is no need to locate the function and call it through a pointer. As an example, suppose that the GL_ARB_multitexture extension is available and you want to call the function glActiveTextureARB. On the Macintosh, this function is already defined and may be called directly. On Windows, a pointer to this function can be retrieved by using the following code.

typedef void (*ATProc)(GLenum target);

ATProc activeTextureProc =

(ATProc) wglGetProcAddress("glActiveTextureARB");

Once this function pointer has been acquired, a small inline function can be defined to emulate a direct function call as follows.

inline void glActiveTextureARB(Glenum target)

{

(*activeTextureProc)(target);

}

This method provides a consistent interface to the glActiveTextureARB function on both platforms. The predefined extension functions will exist only on Macintosh operating systems predating MacOS X. On MacOS X, the OpenGl library will provide a counterpart to the wglGetProcAddress function, and it will be necessary to retrieve function pointers in the same way that it is currrently done on Windows.

Sound

Sound programming differs significantly between the Windows and Macintosh operating systems. On Windows, DirectSound provides the functionality for playing basic sounds and for implementing more advanced effects such as 3D positional audio. On the Macintosh, one uses the Sound Manager to handle their audio needs. The Macintosh Sound Manager by itself does not provide any 3D support, however. Instead, 3D sound is provided by the supplemental use of the SoundSprocket library. Listing 2 shows how to play a buffer of 16-bit, 22.050 kHz, stereo sound on both Windows and Macintosh. Adding 3D effects to this code is beyond the scope of this article, but is not difficult once the level of functionality presented here has been implemented.

The SoundMgr class shown in Listing 2 demonstrates the initialization of DirectSound. The Macintosh Sound Manager requires no initialization, and thus there is no Macintosh counterpart to this class. DirectSound requires that you create a primary play buffer and specify what sound format the hardware should be prepared to play. Sounds are actually played by allocating secondary play buffers which are then mixed into the primary buffer by DirectSound. This is demonstrated in the Sound class shown in Listing 2. The constructor for the Sound class creates a secondary sound buffer and copies the sound information into this buffer (which may actually be on the sound hardware). The sound is then played by calling the Play function. Sounds may be looped by specifying the DSBPLAY_LOOPING flag as the last parameter to the IDirectSoundBuffer::Play function.

Sound is played on the Macintosh by allocating sound channels and sending commands to them. The creation of a sound channel is demonstrated in the Sound constructor shown in Listing 2. Once a sound channel exists, a sound buffer can be played through it by issuing a bufferCmd command. This command carries with it a pointer to an extended sound header, which was filled out by the Sound constructor. This header contains all of the format information necessary for the Sound Manager to correctly play the sound.

Playing looping sounds on the Macintosh is not as simple as on Windows. If you want a sound to play a finite number of times, you can simply issue several bufferCmd’s to the sound channel. (Sound commands are queued and executed only after any previous command has completed.) If you want the sound to loop indefinitely, however, you will have to issue a callBackCmd command which will notify you when a sound has finished playing. When a sound channel is created, an optional callback function may be specified, and this function is invoked whenever the sound channel encounters a callBackCmd. A callback function on the Macintosh has to be specified as a universal procedure pointer, as done in the Sound constructor. (Universal procedure pointers are function pointer abstraction mechanisms left over from the 680x0 to PowerPC transition.) The callback function itself simply issues another bufferCmd and callBackCmd which continue the looping process.

An important issue to keep in mind when playing 16-bit sound is byte order. If you are playing sound from a *.WAV file for instance, you will have to reverse the two bytes in each audio sample before playing them on the Macintosh since these samples are stored in little endian byte order. A single audio sample can have its byte order reversed with the following code.

unsigned short *samp; // Pointer to sample

samp = __lhbrx(0, samp);

This code uses the handy PowerPC instruction lhbrx (Load Halfword Byte Reversed Indexed), which is accessible from C through the __lhbrx intrinsic function. This function loads a 16-bit value and swaps the low and high order bytes.

__________________________________________________

Network Communications