Programming Windows ’95 Games ZAQ (Zack Asked Questions)
Zachary Simpson
April 3, 1996
Overview
This is not a FAQ, it is a ZAQ – a list of questions that I asked when I was trying to convert to Windows from DOS. It is far from a complete guide, but hopefully it will help answer a few of the basic theory and coding questions that don’t seem to be answered anywhere else.
The best source of information about Windows is the Microsoft Developers Network CDs (MSDN). If you don’t subscribe to this, stop whatever you are doing and go get it right now. If you’re too cheap to afford it (like me) then find a friend that subscribes and get an old copy (they come out very often), everything you want with the exception of DirectX will be on any of the old CDs. (In fact, the old ones are more convenient because they were on a single CD).
Another good source of information is the Microsoft Developers Web page located at also gamedev/ on the same site.
Table of Contents
Events...... 2
Is it just me, or is the Windows event system just really stupid?...... 2
What do I do with all this “message pump” stuff?...... 2
What are TranslateMessage and DispatchMessage doing?...... 4
All I want is the damn time of day in milliseconds, is this too much to ask!?.....5
Drawing...... 5
What is the difference between “WinG”, “CreateDIBSection” and DirectDraw?..5
What is frame pitch?...... 8
Do I care about CreateDIBSection now that there’s DirectDraw?...... 8
How do I set up my window? What do I do with the WM_PAINT message?.....9
Has the person who implemented palettes been executed yet?...... 10
Weird Cursor Jumpiness?...... 11
Sound...... 11
Does WaveMix Suck?...... 11
What is DirectSound and do I still need a sound system like AIL?...... 11
Miscellanous...... 12
Windows.h seems to take forever to compile, is something wrong here?...... 12
What are the crazy error messages returned by DirectX?...... 12
Why does Zack have such a bad attitude?...... 12
Appendix A. Event Interface...... 13
Appendix B. Screen Interface...... 16
Appendix C. Sound Interface...... 32
Page 1
Events
Is it just me, or is the Windows event system just really stupid?
No, its not just you; the Windows event system is a mess, and its inefficiencies are exacerbated when trying to write a game. Remember that the system was never really designed to do what you want. Whereas a game typically polls the input devices once per frame, a normal Windows program is entirely event driven. That is, a “good” Windows program is suppose to do nothing until an action is taken by the user. This, of course, doesn’t map well to games where the game is typically doing something like rendering, physics, AI, etc. independent of whether or not the user is feeding input.
One problem with trying to implement a polled system under the Windows WndProc() system is that the messages about the keyboard events come in two separate messages and partially overlap. Furthermore, the #define’s for the key constants are not interchangeable between ASCII characters and key codes. For example, the code for F1 is 0x70 which is the same as the ASCII value of ‘p’. Thus, you can not make a single switch statement that has both F1 and ‘p’ in it. Appendix A. Event Interface demonstrates code which converts all of the values into a homogenous set. Note that this is source from three separate files with functionality deliberately separated to avoid unnecessary header file inclusion.
What do I do with all this “message pump” stuff?
Windows 3.1 was cooperatively multitasked as opposed to preemptively multitasked. In a cooperative system, each application is responsible for relinquishing control back to the operating system before any other application gets any CPU. To demonstrate, suppose that an application goes into an infinite loop. Under 3.1, this application would never relinquish control back to the OS and thus the machine would lock up. Under ’95, however, the OS would task switch away from the process containing the infinite loop and thus other applications would continue to execute. Anyway, the point is that applications under 3.1 needed to have a standard place to return control back to the OS, and in fact, this was done behind your back in the non-obvious GetMessage() function which is, as you would expect, called from the main loop. In fact, in many normal Windows programs, it is the main loop.
int CALLBACK WinMain(HANDLE _hInst, HANDLE hPrevInst, LPSTR nCmdParam, int nCmdShow) {
// Window setup
. . .
// The message pump is also the main loop
MSG msg;
while (GetMessage (&msg, hWnd, 0, 0)) {
TranslateMessage (&msg);
DispatchMessage (&msg);
}
}
The while loop here is referred to as the “message pump”. It causes Windows to check the pending queue of events for events that are applicable to this application. Here’s the weird part: this function only returns when there are messages pending; in other words, this is a blocking call. The return state of GetMessage() is not true if message pending but rather true if message is something other than WM_QUIT. So, now you can see that this function is completely tailored to a traditional Windows application. Clearly, a game would not want to block in the main loop waiting for the rest of the OS. In Win 3.1, a game would write a main loop as follows to avoid the block:
int CALLBACK WinMain(HANDLE _hInst, HANDLE hPrevInst, LPSTR nCmdParam, int nCmdShow) {
. . .
while (1) {
// Poll events without blocking
MSG msg;
while (PeekMessage (&msg, hWnd, 0, 0, PM_NOYIELD|PM_REMOVE)) {
if (msg.message == WM_QUIT) {
return msg.wParam;
}
TranslateMessage (&msg);
DispatchMessage (&msg);
}
// Update the world
// Render the world
}
}
In this case, PeekMessage() is used instead of GetMessage(). PeekMessage() returns what you might expect: true if and only if message pending. The PM_NOYIELD tells it not to block and the PM_REMOVE tells it to toss the message once you’ve extracted it.
Now, just to hammer in the point, note that this is an infinite loop. As I described earlier, an infinite loop under 3.1 would cause all other applications to cease functioning whereas under ’95 the preemptive multitasking will still allow other applications to execute. Despite this, I still use the non-blocking calls under ’95 just so that my application will at least put up a fight!
One thing I do in some games is to automatically pause the application when the user switches the focus to another application. It doesn’t seem fair to let the “bad guys” kill the user when he’s trying to read his/her mail. To do this, you end up with something like:
int foreground;
// This variable tells us if the game is in focused application or not
int CALLBACK WinMain(HANDLE _hInst, HANDLE hPrevInst, LPSTR nCmdParam, int nCmdShow) {
. . .
while (1) {
// Poll events without blocking
MSG msg;
while (PeekMessage (&msg, hWnd, 0, 0, (foreground?PM_NOYIELD:0)|PM_REMOVE)) {
// Block if not foreground
if (msg.message == WM_QUIT) {
return msg.wParam;
}
TranslateMessage (&msg);
DispatchMessage (&msg);
}
// Update the world
// Render the world
}
}
long CALLBACK WndProc (HWND hWnd, UINT message, UINT wParam, long lParam) {
switch(message) {
case WM_ACTIVATEAPP:
// This message is sent when the application is
// either entering the foreground or leaving it.
foreground = wParam;
InvalidateRect (hwnd, NULL, TRUE);
break;
case WM_PAINT:
if (foreground) {
setRepaintAll();
}
else {
// Paint “Game Paused” in the middle opf the screen
PAINTSTRUCT ps;
RECT rect;
HDC hdc = BeginPaint (hWnd, &ps) ;
GetClientRect (hwnd, &rect);
DrawText (hdc, "Game Paused", -1, &rect,
DT_SINGLELINE | DT_CENTER | DT_VCENTER);
EndPaint (hWnd, &ps);
return 0;
}
. . .
}
The meaning of the WM_PAINT message is explained in more detail on page 9.
What are TranslateMessage and DispatchMessage doing?
There are two more lines in the simple message pump that need explanation: TranslateMessage() and DispatchMessage().
The TranslateMessage() call looks for certain combinations of events and translates them into Windows commands. For example, if you have a menu bar with, say, ‘F’ as a hot key, then pressing Alt-F should activate the menu and send a menu command message as opposed to a keyboard message. Generally, you will want to keep the TranslateMesssage() call in the message pump and plan on avoiding any Alt- combinations in your application.
Finally, DispatchMessage() takes the message and calls the WndProc() you registered for your window. Note that this is not the only time when WndProc() will be called – occasionally, the OS will call it directly.
One thing to remember is that if the message pump is not running, you will not receive all events. Occasionally, you may want to call the message pump from some piece of code other than the main loop. For example, sometimes I write a little piece of debugging code that stops and waits for a key so that I can examine some state. In this case, I have a function called waitForKey() which has a message pump and a check for a key press like this:
void waitForKey() {
while (1) {
MSG msg;
while (GetMessage (&msg, hWnd, 0, 0)) {
TranslateMessage (&msg);
DispatchMessage (&msg);
}
if (getEvent(false).isValid()) {
// Uses my eventinterface system described above
return;
}
}
}
One last piece of advice: profile your input code. Some of the calls I wrote in the previous sample are surprising slow. You may need to consider hacking the hell out of this, but hopefully this will at least give you a basic understanding of what’s going on.
All I want is the damn time of day in milliseconds, is this too much to ask!?
Yes, it’s tragic. No, there is no single call that does everything you want. If you want the actually time of day, you have to use GetSystemTime(). Although this function returns a field which is milliseconds, it is actually has a resolution of about 50ms. There is a “multimedia” function (as if only “multimedia” programs need an accurate clock) called timeGetTime() which does indeed return an accurate time in milliseconds, but its value is the time since the system booted instead of the time of day. If you need accurate time for the purposes of profiling, you can use QueryPerformanceCounter() which gives extremely accurate timings in a completely arbitrary units that must be converted to meaningful units with QueryPerformanceFrequency().
void main() {
__int64 start, end, freq;
QueryPerformanceCounter((LARGE_INTEGER*)&start);
Sleep (1000);
QueryPerformanceCounter((LARGE_INTEGER*)&end);
QueryPerformanceFrequency((LARGE_INTEGER*)&freq);
double ms = (double)(end - start) * 1000.0 / (double)freq;
printf("%f\n",ms);
while(1);
}
Drawing
What is the difference between “WinG”, “CreateDIBSection” and DirectDraw?
There is a lot of confusion over the difference between WinG and CreateDIBSection(). The latter was actually created first for Win32 (NT and ’95). WinG was a reverse port of this 32 bit code back to 16-bit Windows 3.1 and was sometimes referred to as “CreateDIBSection for 3.1”. OK, so what is a DIB section and why do you care?
Under the original Windows specification, there was really no good way to create a simple frame buffer without a lot of overhead. For example, in the old way of doing it you had to create a bitmap in the application address space and then copy this bitmap first into the device driver’s address space where it was then often color converted pixel by pixel as it was copied across the bus into the video card’s memory. Needless to say, this sucks. CreateDIBSection(), solved both of these problems. First of all, it creates a frame buffer in shared device-driver/application memory avoiding the extraneous copy. Second, you can create it in the bit-depth that you want (typically 8bpp indexed) and create an “identity palette” that instructs the system-memory to video-memory blt() to avoid any per-pixel color translation. Unfortunately, writing this code is still very cumbersome, but it does indeed work well enough that you can actually play a game in a window if you are only mildly masochistic.
DirectDraw was a latter addition that got rid of a lot of the complexity of creating a frame-buffer with CreateDIBSection() and furthermore gives the game programmer access to a lot of the fun hardware that exists on all modern video cards such as super high speed video-memory to video-memory blts and even esoteric features such as hardware scale and rotate. While most of these features were supported deep down inside of the device-driver layer of window’s GDI (Graphic Device Interface… the API used for drawing spreadsheets, word processors, and other “normal” Windows applications), they were not accessible either directly nor though DOS. Thus, understanding how to take advantage of this hardware can significantly improve the performance of your games versus a DOS implementation. For example, using a “fill blt” and page flipping roughly doubled the frame rate of Wing Command III™ on a 486 at 640x480 when we ported the DOS version to Windows.
The subtitles of all this is a very big subject and beyond the scope of this paper. The best place to start is Animation Techniques in Win32 by Nigel Thompson available in the MSDN as well as WinG Programmer’s Reference by Chris Hecker also in the MSDN. The GameSDK (DirectX API) has a good programmers reference but is short on theoretical explanation.
Here are the basics. Think of your computer and video card as two separate high-performance computers that communicate via a 19th century telegraph line. Obviously, the problem is the very slow connection and thus you want to minimize the traffic across this line, or data bus. Now, the best form of compression would be to say something like, “Hey video card, go fill the rect from 0,0 to 639, 479 with black.” Then, the video card would obey this command at super high speed since it fills the frame buffer with the computer and memory on its side of the bus. This would certainly be a hell of a lot better than touching each pixel independently across the bus. In fact, video cards can do a lot of little tricks like this as long as all the information necessary is on the remote side of the bus. For example, you can say: “Hey, copy (i.e. blt) the data in this rect of video memory to this other place in video memory.” Again, nothing will go across the bus except the command and not only that, but in many cases, the computer on the other side (the video chip) will actually do this operation asynchronously… that is, the CPU doesn’t have to wait for the operation to complete! Other features some of which are implemented on all cards, other which aren’t are: line drawing, pattern filling, scaling, rotating, bltting with a transparent color, alpha blending, and page flipping.
So what do you do with this? The answer, of course, depends on the game. Here’s a couple of ideas:
Space Combat
Suppose that you are writing a space action, let’s call it “Wing Demander III” for the sake of argument.. In DOS, I would allocate a 640x480 frame buffer in system memory. Once per frame, I would:
1) Cleared frame buffer with black.
2) Rendered the stars and ships into the frame buffer.
3) Copy the cockpit sprite and HUD displays on top of this.
4) Wait for vertical blank.
5) Copy the whole damn 640x480 frame buffer across the bus (painfully slow).
Now, the silly thing is that for the vast majority of frames, significantly over half of the completed frame is either black or cockpit. Why should we have to copy this across the bus? With DirectDraw, we allocate two surfaces which page flipped on command. Then, once per frame we:
1) Tell the video card to fill-blt the back surface with black (practically instantaneous and also usually asynchronous).
2) Rendered the stars and ships across the bus into the back buffer.
3) Used the hardware blter to copy the cockpit from another location in video memory (we store it there when we first load). Again, lightening fast and asynchronous.
4) Request page flip. The video card automatically flips at the next vertical blank without us having to wait for it.
As you can imagine, this is significantly faster. The only time that it isn’t faster is in cases where there is significant overdraw (multiple polygons get drawn on top of each other).
Figure 1. The previous frame (gray) has been scrolled up and to the left revealing the two “sliver rects” in white at the bottom and right.
Top-down or Side Scrollers
Now, let’s suppose you want to write a top-down scroller, let’s call it, “Sim-convenience store” for the sake of argument. In games like this, the camera is in a fixed position and the user scrolls around rapidly (or not so rapidly depending on how good the programmers are). Under DOS, the render loop might look like:
1) If the screen has scrolled, shift the back buffer accordingly with a series of memcpy()’s. Dirty the “sliver rects” that are revealed after a scroll. See Figure 1.