CS-360
Fall, 2000
Class 4X
R. Eckert

DirectX and Windows Game Programming

Game Programming

Until 1995 there were no really "good" Windows games. The best games
ran only under DOS. It is really ironic that the GUI-based Windows
operating system is so slow at handling graphics. DOS enables the game
programmer to access the system's video memory directly thus allowing
graphics applications to run very fast. Windows, on the otherhand, uses
its Graphics Device Interface (GDI) to attempt to provide a device-
independent environment which requires all graphics handling to go
through the GDI library of useful, but generally slow graphics
functions. Under standard Windows, no access is permitted to the actual
video hardware. This makes high-speed visual games next to impossible.

Fast games with high-resolution animated graphics are extremely
demanding for a PC. For example, a 640 X 480 screen in which each
pixel's color is determined by an 8-bit value requires a video memory
of almost one-third a megabyte. Imagine one of these 256-color images
as the background scene in a flight simulation game. Because the
background will change in each frame as the aircraft moves, the program
must repeatedly transfer new images to the screen. The minimum rate
would have to be 15 new images per second to provide a continuous
animation. This means about 5 megabytes of graphical data transferred
to the video memory every second.

But things are even worse. In most games there are small objects that
move around on top of the constantly-changing scene. These objects
(called sprites) might include things like the player's on-screen
character and/or his enemies. Sprites are also graphical objects that
must also be transferred to the screen memory. So the program must not
only transfer the background scene to the screen 15 times a second, but
it must also transfer each sprite, one by one, onto this image.

And the above description ignores flicker which can make sprites
constantly appear and disappear. As discussed earlier, flicker can be
avoided by composing a complete scene in memory before transferring it
to the screen. But this essentially doubles the amount of graphical
data that must be transferred because the sprites and background scene
must first be placed in one memory block and then that whole block must
be transferred to the screen. The GDI BitBlt() and StrecthBlt()
functions are hopelessly inadequate to cope with this task.

Microsoft was very much aware of the inadequacies of the Windows GDI
in coping with fast game applications, and in 1995 came out with
something it called the "Game SDK." Subsequently renamed "DirectX," the
Game SDK consists of a series of components called "COM objects." COM
(Component Object Model) is an object-oriented interface developed by
Microsoft for creating objects at the operating system level. A COM
object so closely resembles a C++ class that, when programming in C++,
the programmer can access it in exactly the same way as a C++ class.

DirectX consists of the following components:

DirectDraw--Provides direct control over the computer's video hardware.
In particular, it enables programs to very quickly transfer graphics
between memory and the screen. It is also designed to take advantage of
hardware capabilities that may be present on the video card. And if
certain capabilities are not available on the video card, DirectDraw
can emulate them in software.

DirectSound--Provides an almost device-independent method for directly
dealing with the computer's sound card. It enables the programmer to
easily add sound effects and music to games and to synchronize sound
effects with events occurring on the screen. It also can handle 3D
sound effects.

DirectInput--Provides for easy use of joystick and other game controller
devices in a device-independent way.

DirectPlay--Provides for the implementation of multiuser games over
a network or modem. "DirectPlay provides a transport-independent,
protocol-independent, and on-line-service-independent way for games
developed for Windows to communicate with each other."

Direct3D--Provides optimized three-dimensional capabilities to Windows
games. It also enables games to take advantage of 3D acceleration
hardware, if available, without any additional coding by the game
developer.

DirectX games run under Windows, which means that they can benefit from
all the built-in Windows functionality. In other words, they can use
the GDI graphics functions, all of the Windows user interface
capabilities, all of the fonts and other standard Windows drawing
objects, and, in general, the entire Windows Win32 API.

DirectDraw--

The main purpose of DirectDraw is to provide directly-accessible
drawing "surfaces" in memory and the ability to transfer those drawing
surfaces quickly to the screen. A surface is a block of memory used for
drawing. Usually a separate surface is used to hold each sprite in an
animated scene and another to hold the background. These are then
composed into a final image and transferred to the primary screen
surface.

The following programming steps are normally required in order to use
DirectDraw in a Windows program (check the online help for details on
the use of each DirectDraw function):

1. Call DirectDrawCreate() to create a DirectDraw object.

2. Call the DirectDraw object's SetCooperativeLevel() member function
to get exclusive control over the screen resolution and palette.

3. Call the DirectDraw object's SetDisplayMode() member function to set
the screen's resolution and color depth.

4. Call the DirectDraw object's CreateSurface() member function to
create at least a primary surface and probably one or more secondary
drawing surfaces (called back buffers).

5. Call the primary DirectDrawSurface object's GetAttachedSurface()
member function to acquire a pointer to a back buffer.

6. Call the back buffer DirectDrawSurface object's Lock() member
function to obtain a pointer to the back buffer surface's memory.

7. Draw an image on the back buffer.

8. Call the back buffer DirectDrawSurface object's Unlock() member
function to tell DirectDraw that the program is done with the back
buffer.

9. Call the primary DirectDrawSurface object's Flip() member function
to swap the surface memory associated with the primary surface and that
of the next back buffer surface, thus displaying the newly-drawn image.

10. When terminating the application, all direct draw objects should be
removed by calling their Release() member functions.


The LINESMINIMUM DirectDraw Example Application--

This example creates a 640 X 480 X 8-bit-color primary surface, draws
256 horizontal lines (using the current palette) on a back buffer attached 
to this surface, and flips surfaces so the lines are displayed on the 
screen. It is a Win32 API program that has no menu. The action occurs in 
response to the user pressing the <F1> keyboard key. A press of the <ESC> 
key terminates the application. The application also keeps track of and
displays the time required to draw the lines on the back buffer and
that required for the surface switch. The program uses the various
DirectDraw member functions in the simplest ways possible; in a more
robust "real" application, extensive error checking would be done after
most of the function calls. (Refer to the references.)

A class we've called CDirDraw (specification in cdirdraw.h, implementation
in cdirdraw.cpp) does most of the work in this example. The class defines 
a pointer to a DirectDraw object and two pointers to DirectDraw surfaces. 
Its constructor performs steps 1 through 5 (above), its destructor performs 
step 10, and it has two member functions that do most of the rest of the 
work (steps 6 through 8). The ChangeColor() member function fills the entire
back buffer with the integer given as its parameter. This is a color index,
and in the call that is made to the function from the WndProc() in the main
module (horlines.cpp), the value 0 is used. In the default palette for this
color mode, 0 corresponds to black. The DrawLines() member function actually
draws the lines by filling the appropriate memory locations in the back
buffer with the numbers 0 through 255--all of the color indices in the 
8-bit-color mode being used. The key idea here is that any pixel on the back
buffer can be set to a given color by setting the buffer position
corresponding to that pixel to the given color index value. If, for example
we wanted to set the pixel at (x,y) to color c, we would do the following:
         buf[y*pitch + x] = c;
where buf is the address of the start of the back buffer and pitch is the 
number of pixels in each row of the back buffer. The address of the start 
of the back buffer and its pitch are obtained by calling the back buffer
object's Lock() function, one of whose parameters is a pointer to a 
DirectDraw Surface Descriptor (DDSURFACEDESC) structure. The back buffer
start address and pitch are two of the several members of this structure.

After drawing the lines on the back buffer, step 9 above (flipping the
surfaces to make the back buffer visible) is done in the main program's
WndProc() by making a call to the DirectDraw object's Primary Surface
Flip() function. In order to display the times involved for these
operations, TextOut() is called. But TextOut() (as well as any of the other
GDI functions) requires a handle to the Device Context that is associated
with the primary surface. This handle is obtained by making a call to
the GetDC() member function of the DirectDraw object's Primary surface.
The API function wsprintf() is used to format the integer time information
into a string (cBuf) that can be displayed by TextOut().