1. DirectX API groups
Microsoft's DirectX APIs are made up of the following groups:
2. What is DirectDraw
DirectDraw is essentially a video memory manager. Its most important capability is to allow the programmer to store and manipulate bitmaps directly in video memory. It lets you take advantage of the video hardware's blitter to blit these bitmaps within video memory. Being able to blit from video memory to video memory using the video hardware's blitter is much faster than blitting from system memory to video memory. This is especially true with today's 64 bit video cards that provide a 64 bit data path within video memory. Also, the hardware blit operates independently of the CPU and therefore frees up the CPU to continue working. Additionally, DirectDraw supports other hardware acceleration features of the video card, such as hardware support for sprites and z-buffering. DirectDraw 1.0 is currently available for Windows 95 and will be available for Windows NT this summer.
3. Relationship to WinG
Up until now programmers have used the WinG or CreateDIBSection technology to do fast animation in Windows. This approach gave the programmer direct access to the bitmap in system memory so that one can use optimized routines for drawing to the bitmap. WinG is better than using GDI for all operations that draw to the bitmap, because GDI could never be as fast as the optimized routines used by game programmers.
Once the WinG scene is composed on the bitmap, it is blitted from system memory to video memory, which causes it to be displayed. This technique is not as fast as Direct Draw because blitting from system memory to video memory is slower than blitting from video memory to video memory. Both the DirectDraw and WinG techniques can and will coexist in many complex games or applications since video memory is a limited resource.
4. DirectDraw surfaces - how you access video memory
In DirectDraw, the goal is to put as many bitmaps in video memory as possible. With DirectDraw, all of video memory is available to you. You can use it both to store various bitmaps, and for the primary and offscreen video buffers. All of these pieces of video memory are referred to as surfaces in Direct Draw. When you load a bitmap that will represent a sprite, and store it in video memory, you first create a surface, which is a piece of video memory, and then blit the bitmap into this surface, thereby effectively copying the bitmap into video memory. This bitmap can now live in video memory for you to use, for as long as you like.
The video memory that is currently displaying something onscreen is also a surface, and is referred to as the primary surface. This surface is as big as the amount of memory needed for the current display mode. If the display mode is 640x480 with 256 colors (8 bits per pixel), then the primary surface uses 307,200 bytes of video memory. You usually also create at least one offscreen surface that is the same size as the primary surface for page flipping. This means that you need 614,400 bytes of display memory just to get started, without even loading any bitmaps.
So, one of the most important hardware requirements for fast DirectDraw games is lots of video memory. When you run out of video memory, your DirectDraw surfaces will get created in system memory, and all the benefits of the hardware blits won't be available to these surfaces. Most video cards these days have at least 1 Meg of video memory, however 1 Meg is barely enough to get going. A 2 Meg video card is probably the practical minimum for getting even a fairly simple app working at its best.
DirectDraw provides functions for determining at run time the amount of video memory available, so the application can optimize its use of video memory.
5. The page flipping approach to animation
A scene is composed by blitting the bitmaps from their video memory buffer (surface) to another offscreen buffer (surface) also in video memory. The scene is then displayed by using the hardware's ability to flip the visible video surface to the offscreen surface so that the newly composed scene is now visible. This is known as page flipping and it is very fast. A page flip is fast because there is no memory copying involved. It is simply a matter of setting a register on the video card that tells it where to scan the video memory that will be used to send the signal to the monitor. DirectDraw shields the programmer from the hardware specifics of such operations, and provides a very simple Flip() API function to do the work. To do animation with page flipping:
Graphics programmers have developed some sophisticated techniques for optimizing the performance of sprites, and these techniques can still be used in DirectDraw applications. However, learning these techniques won't help one learn or understand DirectDraw. I will, therefore, focus on the page flipping approach to animation. Page flipping is probably the easiest approach to animation, as well as the best one for demonstrating how DirectDraw works.
6. DirectDraw and COM
DirectX is implemented using COM objects. Using COM objects in DLLs has many benefits over simply exporting flat APIs from DLLs, including allowing for polymorphism and versioning. The main thing for the DirectDraw programmer to know is that using the COM objects is no harder than using any other API, especially from a C++ or Object Pascal app.
You don't need to mess with initializing
COM, or calling QueryInterface to get the interfaces. The DirectDraw header
file declares the C++ classes for the various DirectX objects. Instances
of these classes are created for you by calling various create functions.
From there its simply a matter of learning the various member functions.
1. What you need to compile and link
First and foremost, you will want the DirectX SDK. This is currently available only on MSDN level II or above, and with MS Visual C++ 4.1. The SDK provides a descent help file, and excellent example programs.
To compile and link a DirectDraw application, you need the DDRAW.DLL, DDRAW.H, and you need to create an import library from DDRAW.DLL with the IMPLIB.EXE utility. Since DirectX is only a Win32 technology, you will need a compiler capable of generating Win32 apps. Borland C++ 4.52 and Borland C++ 5.0 provide a great platform for DirectX development.
To run DirectX technology, you must have the DirectX drivers installed on your system. Since this is a commonly used gaming technology, you may find that DirectX is already installed on your system. Look in your Windows/System directory to see if you have a copy of DDRAW.DLL already installed on your system. (Remember, this technology will not work on the 3.X versions of Windows NT.)
2. Initializing DirectDraw
The first thing to learn about DirectDraw
is how to initialize it. Read the detailed comments in the following source
code for explanations at each step.
We have now initialized DirectDraw and set the display mode.
3. Creating the primary surface and setting up for page flipping
Next we create the primary surface
with one back buffer. This is called a complex surface in DirectDraw. The
BackBuffer is "attached" to the primary surface. When the cleaning up at
the end of the program, you only need to call Release on the primary surface
pointer, and the backbuffer is freed for you.
We have now created our double buffered page flipping environment.
4. Loading a bitmap into video memory
The next step is to load the bitmaps
for the game. Ideally there is enough video memory so that all the bitmaps
live in video memory. If not, CreateSurface will create the surfaces in
system memory for you. Everything will still work, the blits from these
surfaces will simply be a bit slower.
At this point we have a fully functioning DirectDraw surface with a bitmap loaded into it. We will use this surface later to blit it onto the scene we are composing.
5. Loading the palette
We need to set the palette for the
primary surface. This implies that all of the bitmaps that are created
for the game should all have been created with the same palette. Here is
a function to create a DirectDraw palette for a given bitmap.
Next we set this palette to the primary
surface like this:
6. Color Keying
DirectDraw uses the concept of color
keying to allow for using transparent colors during blits. That is, you
can make part of your bitmap appear to be transparent, which is a useful
technique to use when blitting sprites onto a background surface. The color
key specifies a range of palette entries in the source or the destination
bitmap that will not be copied during a blit. Typically you set a the 0th
or the 255th palette entry to be the transparent color on the source bitmaps,
and also construct our bitmaps such that the areas that need to be transparent
use this color of the palette.
7. Composing the Scene
At this point we are ready to start
creating a game, simulation or other graphics based program. The simple
approach is to blit a background bitmap onto the back buffer, then blit
some sprites (also just bitmaps) onto the back buffer at their appropriate
locations, and flip. Then repeat the above steps, blitting the sprites
into new locations.
A couple of things to note here. In the calls to BltFast(), and Flip(), the last parameter included a DDXX_WAIT flag. This is necessary because the each of these functions is asynchronous. That is, they will return when the operation was successfully started (but not yet finished), or when an error caused the function to not be able to start successfully. The most common error returned is an error indicating that the surface is busy completing a previous operation on the surface. You use the DDXX_WAIT flag to tell DirectDraw to keep trying the operation as long as the surface is busy, or until some other error occurs.
Notice that the first call to BltFast(), the one that is blitting the background, uses the flag DDBLTFAST_NOCOLORKEY. This tells BltFast to ignore the color key, and can improve the performance of the blit on some cards. This means that any transparent areas on this bitmap will be copied and therefore not be transparent, but this is OK since this is the background bitmap and not a sprite like image.
When the Flip() function is called, the surface objects associated with the video memory underneath are swapped. This means that what was once the primary buffer is now the backbuffer and vice versa. The result is that you can just keep using your backbuffer pointer to compose the scenes, and call Flip(), without having to worry about keeping track of where the buffers are.
8. Cleaning up
To cleanup in DirectX, the main thing
is to call the Release() member function on any DirectX object you created.
You would do something like this:
When you call release on the actual
DirectDraw object (the instance of LPDIRECTDRAW) the reference count will
go to zero, and the object will be destroyed. When this happens, the original
screen mode is restored.
When a DirectDraw app is running full screen, GDI is not available. This means that you cannot set breakpoints in your app and expect to see the debugger when they are hit. There are some fancy solutions involving debuggers that support dual monitors, or remote debugging, but the simplest in terms of hardware requirements is to simply write your app such that it can run windowed as well as full screen.
2. Running Windowed
When you want to run windowed you must initialize DirectDraw differently. Since you have to coexist with GDI, you are not allowed to change the display mode. The main thing to keep in mind is that when you create your primary surface, you are getting a pointer to the display memory currently visible and in use by GDI. You can, if you want, scribble anywhere you want on the screen. So you have to be careful to keep your drawing inside of your window. You can use a DirectDraw clipper to help with this if you like.
The following function initializes
DirectDraw for running in a window, and doesn't use page flipping.
The first difference between this code and the previous code for initializing DirectDraw is that you call SetCooperativeLevel() with the flag DDSCL_NORMAL. This means that we are not taking complete control of the display and therefore not removing GDI from the picture. When we create the Primary surface, we don't create a complex surface, because we are not preparing for page flipping.
Next we create a DirectDraw clipper for our window and attach it to the primary surface. The DirectDraw clipper is an object used to maintain a list of rectangles for clipping. By setting the clipper to our window handle, we are implicitly telling the clipper to clip using the client area of the window as the rectangle to clip. We then set this to our primary surface, so that blits to this surface are clipped per the client area of our window.
We create the backbuffer separately, and this time we must specify the size.
Now we are set to go. The last thing
we need to do differently is handle the code where we would normally call
Flip(), and instead do a blit. You can brew your own Flip that does the
right thing like this:
3. Losing Surfaces
One very important area of robustness that the code samples above ignored is what happens if the user switches away from a fullscreen DirectDraw app to some other app running in the system. First of all, your code needs to handle the WM_ACTIVATEAPP message and set a flag when your app goes inactive. In the idle section of your PeekMessage loop, check this flag, and don't try to update your display when you're not active.
But the real issue is that once you
switch to another app, you have left the DirectDraw environment and allowed
GDI to become active. GDI is oblivious to DirectDraw, and will overwrite
the areas of display memory in use by your DirectDraw surfaces. This means
that your bitmaps get wiped out and will need to be reloaded. Fortunately,
when you call a function that operates on a surface, such as Flip() or
BltFast(), DirectDraw will return an error of DDERR_SURFACELOST to let
you know if the surface was lost. So, your code has to check for this error
and reload the bitmaps into the surfaces if this error occurs.
4. Further Reading
The best reading I can recommend is the sample code that comes with the DirectX SDK. These samples are worth looking at as they cover some areas of robustness that this paper ignored. I have yet to find a book on DirectX that exposes anything advanced in the API. MSJ has published a couple of very good introductory DirectDraw and DirectSound articles, but nothing very advanced.