![]() |
![]() | |||||||||||||
![]() | ||||||||||||||
| ||||||||||||||
![]() |
![]() |
|
Initializing a DirectDraw applicationClick here to download an offline version of this tutorial. To download the source code of the program we create in this tutorial click here. Note that we'll use the error reporting class for DirectX created in the 'Creating a DirectX error reporting system' tutorial. I suggest you to have a look at that tutorial now if you haven't already done so since we'll use the error reporting class created in the latter tutorial in this tutorial. General steps for every DirectDraw applicationThere are a few things you'll have to do every time you'll code an application which uses DirectDraw:
Creating a basic Windows applicationIn the 'Introduction to programming in Windows' tutorial we already created the framework for a basic Windows application. I suggest you to use the source code from the latter tutorial as a framework for the program we'll create in this tutorial. Be careful though. In the 'Introduction to programming in Windows' tutorial we created a normal Windows window. However, most DirectDraw applications don't run in a Windows window with menu options etc. That's the reasons we'll now create a simple popup window instead of a normal Windows window. If you need a Windows window with menu options you can always use the code from the latter tutorial. We create an InitApp function, and use this function to initialize our application. Our InitApp function becomes (for the moment):
This InitApp function is basically the same as the one used in the 'Introduction to programming in Windows' tutorial. Like I've mentioned before there are some (minor) differences. We've created a popup window instead of a normal window (for reasons mentioned above), and we now check for errors, so that when an error occurs we write an error message to an error log file. We do this with our previously created error reporting class (see the 'Creating a DirectX error reporting system' tutorial). If you don't fully understand the above code either refer to the 'Introduction to programming in Windows' tutorial or mail me. Creating a DirectDraw objectFor every DirectDraw application you code the first thing you have to do is creating a DirectDraw object. The DirectDraw object is the heart of every DirectDraw application, and you have to use it to create all other related DirectDraw objects. You create a DirectDraw object by calling the DirectDrawCreateEx function. There also exists a DirectDrawCreate function which does basically same thing, namely creating a DirectDraw object. However, the DirectDraw object created by DirectDrawCreateEx has more capabilities, so we use this function. To create an DirectDraw object we first declare a global variable which will hold the address of our DirectDraw object:
We've now created the g_DD variable which will hold our DirectDraw object. Note that I precede every global variable I declare with the g_ prefix, so it's easier later on to see whether you are working with a local or a global variable. Now for the creation of the DirectDraw object (we do this in our InitApp function):
Let's have a closer look at the above source code. The first thing you might be wondering about is the FAILED statement. The FAILED statement is actually a macro. The FAILED macro, and its counterpart, the SUCCEEDED macro, provide a generic test for failure (or success) on any status value. Using those two macros is the way to check whether a DirectX function has failed or succeeded. Another method to check for success of a (COM) function is to check whether the function returned a DD_OK constant. This is however not the best method, because it is possible for COM objects to have multiple return values as success values. Something to be aware of is that some DirectX functions return failure codes when they succeed. An example (where I have to credit David Joffe for) is IDirectPlay::GetPlayerData which will fail with DPERR_BUFFERTOOSMALL when you ask for the data size. So be aware. When we look at the DirectDrawCreateEx function call we can see this function requires four arguments. The first argument is an address of the globally unique identifier (GUID) that represents the display driver to be created. You normally have to set this parameter to NULL to indicate the active display driver. The second argument should be a LPVOID pointer (that's the reason why we convert &g_DD with the (VOID **) cast) to the DirectDraw object you create. The third argument and the fourth argument should always be set to IID_IDirectDraw7 and NULL respectively. When the DirectDrawCreateEx function fails, we write an error message to the error log file. With the second argument of WriteErrorMessage, which is set to true, we instruct the error class to quit the application after it has written the error message to the error log file. That's all you have to know about the creation of a DirectDraw object. Let's move on to our next subject; setting the cooperative level. Setting the cooperative levelYou might be wondering what exactly the cooperative level is. Well, the cooperative level specifies how DirectDraw interacts with the display and how it reacts to events that might affect the display. For example, you use the cooperative level to tell DirectDraw whether your application runs as a full screen application with exclusive access to the display or as a windowed application. Most of my DirectDraw applications run as full screen applications, so we'll tell DirectDraw to create a full screen application with exclusive access to the display:
Note that SetCooperativeLevel is a member function, a method, of a DirectDraw object. Again, we use the FAILED macro to check whether the SetCooperativeLevel method failed or succeeded, and we use WriteErrorMessage to write an error message to our error log file when SetCooperativeLevel failed. SetCooperativeLevel has two parameters. The first one should be a window handle for the top-level window of your application. The second one should contain (combinations) of flags instructing DirectDraw how to interact with the display. Here we use a combination of DDSCL_EXCLUSIVE and DDSCL_FULLSCREEN because we want our application to run full-screen with exclusive access to the display. If you want your application to run in a normal Windows window you should use the DDSCL_NORMAL flag instead of the DDSCL_EXCLUSIVE and DDSCL_FULLSCREEN flags. Switching the video modeBefore your DirectDraw application starts you should switch the video mode to the mode your DirectDraw application was designed for. You'll have to do this with the SetDisplayMode method of the DirectDraw object:
SetDisplayMode requires five parameters. The first parameter should contain the width of the new mode and the second one the height. The third parameter should be set to the bits per pixel value of the new mode. The bits per pixel value indicates how many bits should be reserved in video memory for every pixel. Basically this means, the more bits per pixel the more colors the video mode can handle. For example, if your bits per pixel value would be 8 the video mode can handle 28=256 colors. If you don't understand this or you want a more extensive explanation refer to the tutorials of David Joffe (see the links section). The fourth parameter of the SetDisplayMode method indicates the refresh rate of the new video mode. We set the value of this parameter to 0 to request the default refresh rate for the driver. The fifth parameter after all is used to pass additional flags to the SetDisplayMode method. DirectX 7.0 only supports the DDSDM_STANDARDVGAMODE flag which causes SetDisplayMode to switch to the good ol' mode 13h. We use 0 (NULL is also possible) here for the fifth parameter to indicate that we don't use this feature. Creating the front and back surface(s)Every DirectDraw application requires at least one front surface and most of the time also back surface(s) to draw to the screen. Refer to the 'Introduction to DirectDraw' article if you want a more detailed explanation of surfaces. I'll first give you the source code which creates a front surface and one back surface:
The first thing you might be wondering about is the sd structure we're using. This structure contains information of the surface we're going to create, and we'll have to pass it on to the CreateSurface method with which we'll create the actual surfaces later on. The sd structure is a structure of the type DDSURFACEDESC2. We create it via the following line of code (place it at the start of the InitApp function):
We start creating the surfaces by filling the block of memory occupied by the sd structure with zeros via the ZeroMemory function. This function requires two parameters. The first one being an address of the block of memory to fill with zero's and the second one a number specifying the amount of bytes to fill with zero's. Filling a structure with zero's before you use the structure is usually a good habit and I suggest you to do this with every structure you'll use in the future. After filling the sd structure with zero's we set its dwSize field to the size of the structure. Always initialize this field before you use the structure! Next we set the dwFlags field. We use the DDSD_CAPS flag to indicate that the ddsCaps field of the sd structure is 'valid' (i.e. that we'll use it). We also set the DDSD_BACKBUFFERCOUNT flag to indicate that besides the ddsCaps field we'll also use the dwBackBufferCount field. After this we assign a value to each field we've indicated as being 'valid' via the dwFlags field of the sd structure. We first set the ddsCaps field. The ddsCaps field is again a structure on itself with one field, namely dwCaps. This field is used to specify the capabilities (caps in shorts ;-) of the surface we're about to create. We use three flags to specify the capabilities of our surface:
Like I've mentioned before we set the dwBackBufferCount field to one since we want to create one back surface. After we've specified all the desired options of our new surfaces it's time to create them. We do this with the CreateSurface method. CreateSurface requires three parameters. The first parameter should be an address of the structure describing the requested surfaces, thus the address of the sd structure. The second parameter should be the address of a pointer to a DirectDraw surface. Here we use the address of the global variable g_DDSPrimary of the type LPDIRECTDRAWSURFACE7 which will hold a pointer to our front surface. You should declare this variable at the beginning of your program, as with any other global variable. The following line of source code shows you how to do it:
The third and last parameter of the CreateSurface method should always be set to NULL. So far for the creation of the front surface. We now have a pointer to our primary (front) surface, namely g_DDSPrimary. We however still lack a pointer to a back surface, which we need for page flipping purposes. The last lines of code create a back surface for us. To create an additional back surface we need to use the GetAttachedSurface method to retrieve the additional surface we've created with CreateSurface (remember we specified via dwBackBufferCount and the DDSD_BACKBUFFERCOUNT flag that we wanted to create an additional back surface). GetAttachedSurface is a method of the primary surface we've just created (I know this sounds confusing, just think of the surface as an object which besides its ability to display graphics, also happens to have various other methods). To use the GetAttachedSurface method we need an additional structure of the type DDSCAPS2 which defines the capabilities of the new attached surface . (We've seen this structure before as a member of the sd structure). We'll name it ddsc. Just like we've done with the sd structure we fill the entire structure with zero's using the ZeroMemory function before we use the structure. The ddsc structure has one field, namely dwCaps. We assign the DDSCAPS_BACKBUFFER flag to the dwCaps field to indicate we want to use the attached surface as a back surface of a page flipping system. That's all (and enough ;-) for the creation of the surfaces. Many other schemes are possible, for example two back surfaces instead of one, or the use of overlay surfaces. These advanced issues will be dealt with later on. ConclusionSo far for the initialization of a DirectDraw application. Have a look at the source code I've printed out below to see where all the functions and variable definitions should be placed. Just copying and pasting the code below won't result in a working application since you'll need the code for the error reporting class we use for this application. To get a working copy of the program we've created in this tutorial download the source code (including the source code for the error reporting class).
The application we've created in this tutorial isn't of much use :-(. There is no fancy animation going on or something like that. The only thing we've done in this tutorial is creating a basic framework for a DirectDraw application. We'll use this framework later on to build DirectDraw applications which also do something. If you don't understand what I've just explained (which isn't a shame, since we're dealing with a difficult subject here) I suggest you to read the entire tutorial over again. Try to understand every sentence I've typed. Remember that if you still don't understand what I've explained to you, you can always mail me.
|