[ 32bits.co.uk ] Coding Direct3D for real people

32Bits - Coding for real people!

.:[ Please always use http://www.32bits.co.uk to access the site. Thanks! ]:.

32Bits main page Introduction to the site The tutorials index Our releases The license for content on this site Public forum for questions and feedback We provide professional services Contact us

 

Last updated: 10:55 AM Monday October 2008

View the errata, additions and corrections to this tutorial


This tutorial is exactly the same as the DirectX 8 version. There is nothing specific to DirectX 9 here.

And thats enough of that. You've been greeted and walked gently through this site, but now it's time to get down to the code. Lets discuss Windows coding - the Win32 API. In a Windows environment, the Win32API is the one single way C++ programmers can write applications that work. Even if you use MFC, if you dig down to the lowest level of source code, you'll find that it is just a pretty wrapper around the Win32API. So, what is the Win32API? Think back to the days of DOS. There you were, limited to one single shell instance, and could only run one command/tool/application at any one time. This was bad, but it was all we were used to. But then came Windows with it's extremely powerful multitasking. When this came along, it was the dogs. People were finally able to run more than one application at once, totally despite the limitation of the human brain only being able to do one thing at a time. This was what people saw. What they didn't see was the background multi-tasking. You were finally able to leave Excel calculating formulas and go play Solitaire while you waited. Despite a form of multitasking being present since the DOS days (TSRs in effect allowed you to load a process into the background whilst retaining control of your C: prompt), Windows brought it to a whole new level. Add a nice little GUI to that, and you're made. But then you know all this, so why am I going over it again?

Well, think about this. There you are with Excel running in the background churning out answers to your equations, whilst you're playing Solitaire in the foreground. Ever wondered how it works? I mean, you've only got one processor, one lump of RAM, one graphics card etc, so surely each application should be fighting the others for control of the system? This is where some clever Windows trickery comes in. Windows allocates each running process a certain amount of time to complete some work in. When the process has used it's timeslice up, Windows pauses it and lets the next process in the list have its time. This goes on until all the processes have been given a timeslice, and then it restarts with the first process. But, just when you thought Microsoft had done a good job, they pulled another trick out the hat. If you are sitting there playing Solitaire whilst waiting for Excel, it doesn't make any logical sense to give Solitaire (where you might be watching the screen, doing nothing & thinking) the same amount of time as Excel (which is working it's ass off). Because of this, each process can be allocated a priority. These priorities range from REALTIME to IDLE. This means that an application which really needs the processor time can increase it's priority, whilst Solitaire can sit at normal priority and just get it's timeslice when Windows has a chance.

Now, on top of this Windows needs to make sure the correct applications get hold of the correct signals from the keyboard, mouse and the Windows subsystem itself. These signals are grabbed by Windows in the first instance, sanitised, and then dispatched to the appropriate process in the form of "Windows Messages". They get fed into the process' "message loop" via a callback function, and the application itself decides whether to process them or ignore them. This callback function is registered with Windows when the application first runs, so it already knows where to send the messages. Because Windows is controlling which application gets what message, the app itself doesn't need to worry or care about other processes running on the system, to an extent. I say "to an extent", because advanced Windows coding definately does call for some care and appreciation of what's going on outside your own process. But for our purposes, we don't care - it's DirectX we're interested in, and we don't need an in-depth knowledge of win32 for it.

Have a look at this diagram, taken from "Programming Windows With MFC".


Fig 1.1: The Windows Message System

At the top you can see the WM_ messages being generated by Windows and placed in the message queue. These messages then filter down to the application's message loop, which is basically a massive switch statement to respond to different messages. Any messages the application doesn't want to deal with is passed to DefWindowProc - Windows Default Message Handler - which provides standard responses to unhandled messages. This is what enables us to write minimal code, but to present an application window that functions in the way you would expect - as Windows handles the default behaviours for resizing, dragging, system menus etc and many more, you can garantee a common response across different machines with the same codebase. Take note of the dotted rectangle in the diagram, as this is what our application is responsible for.

Make absolutely sure that the default behaviour for your message loop switch statement is to call the win32 DefWindowProc. If you don't do this, you'll notice it right away - your application window will lack all the features of a normal Windows app!

If you're like me, you prefer seeing things in practice. It's easier to understand when you can actually see something, instead of looking at an abstracted diagram. So, without further ado, here is the complete code for creating a window. It's a lot to take in, especially if this is your first dip into Windows coding, but stick with us. Run your eye over the code so you can see the structure as a whole before we take it apart.

[ View source code in this window ] [ View source code in a new window ] [ Source code in plain ascii .cpp file ]

It's a fair bit of code just to put a window on the screen. 100 odd lines of code, and the net result is this:


Fig 1.2: Our window. Woot.

Not very impressive, granted. But it's very important. Get this right first time, and you'll probably never need to worry about it again. Whats more, get it right the first time and, if you do have to come back to it, you'll know exactly what you're doing. So lets step through the source code. Here's the first bit of code we need to discuss:

#define WIN32_LEAN_AND_MEAN
#include "Windows.h"

// Function declarations
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow);
LRESULT WINAPI MsgProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam );

// Globals
static char strAppname[]="Our First Win32 App!";


This is all quite straightforward. We tell Windows to cut down the amount of stuff it links into our exe with the #define WIN32_LEAN_AND_MEAN, and include Windows.h - the header file with all the windows related stuff in it. Next we declare our only 2 functions - WinMain, which is the entry point for the application (ie: the first function that runs when the app is launched, just like Main() in a console app), and MsgProc, the function that handles all the messages in diagram 1.1. Lets continue:

WNDCLASSEX wc;

ZeroMemory(&wc, sizeof(WNDCLASSEX));
wc.cbSize=sizeof(WNDCLASSEX);                       // size of the window struct in bytes
wc.style=CS_HREDRAW | CS_VREDRAW | CS_OWNDC;        // window styles to use
wc.lpfnWndProc=MsgProc;                             // function name of event handler
wc.hInstance=hInstance;                             // handle to this apps instance
wc.hbrBackground=(HBRUSH)GetStockObject(GRAY_BRUSH);// background colour of window
wc.hIcon= LoadIcon(NULL, IDI_APPLICATION);          // icon for the app window
wc.hIconSm=LoadIcon(NULL, IDI_APPLICATION);         // icon when minimized to taskbar
wc.hCursor=LoadCursor(NULL, IDC_ARROW);             // cursor to use for this window
wc.lpszClassName=strAppname;                        // name for this class

// Register the window class
RegisterClassEx( &wc );


Here we create an instance of the window class, and fill out it's members. WNDCLASSEX is a struct with a fair few members in it, so we use ZeroMemory to set every member of the struct to 0. This lets us fill out only the members we want. The comments are pretty self-explanatory, each member of the struct can take lots of different settings so have a look in MSDN for more info. Once we've set the WNDCLASSEX members to our preferred settings, we register the class with Windows. A few points to note. In the wc.style member we've set the CS_HREDRAW and CS_VREDRAW flags. These tell the win32api to completely wipe the contents of the window before redrawing. It's not so much a problem with DirectX, but if you are using the GDI to draw into the window for straight Windows applications, these flags might produce a nasty flickering/flashing on the redraw. To eliminate it, just remove the flags! As I said, a dip into MSDN turns up lots of useful things, bookmark it now! Another member you should take note of is the wc.lpfnWndProc member. As you can see, we pass it the function name of the message handler for our application (you should know why passing just the function name without brackets gives a pointer, if not go revise your C++!). It's important that should you change your message handler function's name, you change this member as well.

As you can see, the first thing we do is to use ZeroMemory to wipe the contents of the WNDCLASSEX structure. We'll be doing this a lot with DirectX. Many of the DirectX functions take custom DirectX types (such as D3DPRESENT_PARAMETERS) which have lots of members. Most of the time we only need to set a small percentage of these members, and it's sufficient to leave the rest of them blank. To do this, you must explicitly call ZeroMemory to set all the members to NULL. If you don't do this, unwanted random data will remain in these unset members and potentially cause your application to crash. So, when creating an instance of a "custom" type, make sure you ZeroMemory it first. The prototype for it is ZeroMemory(&customtype, sizeof(customtype)).


Moving on.

// Create the application's window
HWND hWnd = CreateWindow(strAppname, strAppname,
                         WS_OVERLAPPEDWINDOW, 100, 100, 640, 480,
                         NULL, NULL, wc.hInstance, NULL );

// Show the window
ShowWindow( hWnd, nCmdShow );
UpdateWindow( hWnd );


Again, fairly straight forward. We call the API function CreateWindow passing it the class name, the title for the window, the window styles & size and an hInstance parameter (taken from our WNDCLASSEX struct). The NULL parameters are used in place of options we don't need, such as giving it a parent window etc. CreateWindow returns an HWND - a handle to a WND - which allows DirectX to talk to the window later in our code. Next, ShowWindow displays the window on screen. Notice the nCmdShow parameter. If you scroll up to the function declaration, you'll see that nCmdShow is actually a function paramter of WinMain. This is an int that Windows passes WinMain to tell it how to open the initial window. So, if you right click an application icon on your desktop you can tell an application to start "Normal", "Maximized" or "Minimized". These options are converted to an integer and passed to WinMain for your app to act on. In this instance you can see that we are choosing to ignore whatever the value of nCmdShow is, and just pass it straight to ShowWindow. Therefore our app will obey any settings in it's icon properties. This also raises the possibility of us forcing a change to the value of nCmdShow in our code so that our app always starts minimized, maximized etc.

      // Enter the message loop
     MSG msg;
     ZeroMemory( &msg, sizeof(msg) );
      while( msg.message!=WM_QUIT )
     {
          if( PeekMessage( &msg, NULL, 0U, 0U, PM_REMOVE ) )
         {
              TranslateMessage( &msg );
              DispatchMessage( &msg );
         }
          else
          {
              // no winmessages, idle code here
          }
     }

     UnregisterClass( strAppname, wc.hInstance );
      return 0;


Now we get into the message loop. First of all we create a MSG struct to hold the Windows messages. Next we do the standard ZeroMemory, and then enter the loop. As you can see, the loop continues until we receive a WM_QUIT message. Now that we're in the message loop, our app has to grab the messages off the queue. It does this by using PeekMessage. Note the PM_REMOVE flag. This tells Windows that when we've grabbed a message off the queue it should be automatically removed. There are a few possible flags you can use here to modify the behaviour of the message queue. One of the more useful ones is to peek into the queue without removing the message thats sitting there. This means that during a period of intense processing you can check the queue and see whats waiting - if it's urgent you can use a "Peek and Pump" mechanism to ensure the message is dealt with during the processing. Imagine you had a looped routine in your code that drew 10,000 squares on screen. If it takes 1 second to draw 1,000 squares, your application will "hang up" for 10 seconds whilst the looped routine completes. This means that none of the messages will be processed, therefore dragging the window, resizing, quitting etc could potentially take up to 10 seconds to be processed. Using a Peek and Pump mechanism, you can increase the responsiveness of your app considerably. At every iteration of your loop, the app could peek into the message queue using the PM_NOREMOVE flag, see what's waiting and depending on the message type call the appropriate function to act on it. Don't worry if this doesn't make immediate sense!

If PeekMessage returns TRUE, the message is first translated. For this little app, it's not strictly necessary to call TranslateMessage (which translates keypresses on non-character keys into virtual keycodes) but as this will be the basis for our future DX code we'll do it anyway! Next, DispatchMessage sends the message retrieved from the queue to our MsgProc function. This actually goes via the win32API before being sent to our MsgProc() func - this is why a pointer to the function was part of our WNDCLASSEX struct earlier. At this point, we've done everything necessary to grab messages and get them passed to our application. One further thing, notice the else statement there. This conditional is only entered if there are no messages in the queue - effectively meaning the application is idle. It's a good opportunity to use this to do some background processing if we need it, as if you structure it right and don't do too much it can be a very efficient way of performing non-critical tasks.

As a point of note, this used to be the best way to perform tasks in the background, or "idle processing". However most coders now launch a seperate thread at idle priority, and do their processing there. This has it's advantages and disadvantages. It's great because Windows will allocate it a timeslice totally seperate to the main thread, and you never have to worry about doing too much in an idle processing routine. It's very efficient, and if you are running on a multiprocessor machine the thread can execute in parallel to the main thread. However the disadvantages are it gets very complex and tough to control - if your background thread is acting on data received or shared from the main thread you need to worry about race conditions and exclusive access. In a sentance, because both your main thread and your background thread are running at the same time, you need to ensure any variables they both have access to are not accessed at the same time. To do this you need to implement some form of "blocking", such as a critical section, mutex or semaphore. This is getting into pretty advanced Windows programming, so don't dwell on it too much - we won't have any need to use multithreading in our DX applications. But if you'd like to learn more, search MSDN for articles relating to multi-threaded applications.

Our final action is outside of the while loop. The only reason we quit the loop is if a WM_QUIT message is received, which is sent when the application needs to exit (either from our own code, or from the win32api). In that case we simply unregister the window class, and exit the application. Simple, eh! Lets finish up.


LRESULT WINAPI MsgProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam ) { HDC hDC; PAINTSTRUCT PaintStruct;

switch( msg ) { case WM_PAINT: { hDC=BeginPaint(hWnd, &PaintStruct); // Tell windows we want to update the window // Do GDI drawing here EndPaint(hWnd, &PaintStruct); return 0; }
case WM_DESTROY: { PostQuitMessage( 0 ); return 0; } default:
return DefWindowProc( hWnd, msg, wParam, lParam ); } }

Not much left to go! Thats the message processing function in it's entirety, and it's pretty simple. This function is actually more complicated than it needs to be, because I've added in an extra bit of code as an example (the extra code is highlighted with this style). As you can see, we just switch on the message type, and perform different actions as appropriate. If the message is WM_DESTROY, we use the PostQuitMessage function which cleans up our application resources in the win32api and posts a WM_QUIT message to the message queue (which, as we saw above, is handled in the main message loop). If we haven't provided an action for the message in the switch statement, we use the API DefWindowProc function to let Windows provide a default response to the message (if any) - this is what lets us resize and move the window (amongst other things) without any of our own code.

Lets look at the WM_PAINT handler. It's pretty simple, but will be useful to us later on. WM_PAINT is the message sent by Windows when we need to update the display. So, first of all we tell the API we want to begin drawing to the window, and to give us a DC (Device Context). You can see that one of the parameters we pass is hWnd - a var of type HWND (or "Handle to a WND struct"). This HWND is exactly the same as the HWND we receive in the WinMain function call to CreateWindow, so if you have multiple windows and create your HWND's with global scope, it's a quick way to check what window you need to draw into. Again, we won't require this for our DirectX creations. Next we have a call to EndPaint. As you can see by the comment sandwiched between BeginPaint and EndPaint, this is where you do all your custom GDI drawing. In our future DX apps, we'll be using this to output some statistics such as framerate - it's easy to use the GDI in conjunction with DX.

It's important that you sandwich any GDI drawing between calls to BeginPaint and EndPaint. These are the API functions that ensure you have exclusive access to the device context that allows you to draw into your window on screen. If you don't do this, you'll be writing to protected memory and many bad things will happen. Remember that even though we're outputting graphics to screen, we're just shifting bits around in memory. Just as with anything, if 2 objects try to write to the same memory area at the same time, much badness occurs.

So there you have it! Your first win32 application. It doesn't do much, but if it's on the screen, working, and most importantly you understand what we've just created, you're on the right track. Have a play around with the source, change some stuff and see what happens. If you get stuck, and the answer isn't on this page, MSDN is your friend. Have a look here for a complete explanation of how to create a message loop. It's Microsoft, so be prepared to do things in a far more complicated way! Good luck, and see you in part 2 for something far more DirectX related!


Click here
to send feedback, bug reports, comments and ideas etc!


Article proofed by Ravian & Sol^Trauma

 


Corrections, Additions, Errata

14/05/2002: Markus Burkert pointed out the spelling mistake approximately halfway through the tutorial, discussing window creation. The sentance "In this instance you an see" should have read "In this instance you can see". Thanks!

29/05/2003: Niall Rore mailed in a spelling mistake in the first couple of paragraphs. I have now learnt how to spell the word "equation". Thanks Niall!

24/01/2004: Peter van Hardenberg noticed that Microsoft are out to get me, and have moved their Win32 sample application page. I've updated the link in the last paragraph - thanks Peter!

31/01/2004: Thomas Femino told us the second to last sentance of this tutorial didn't actually make any sense. He was right, and now it's fixed!