🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

04.02 - Bringing Up DirectDraw

Started by
26 comments, last by Teej 21 years, 11 months ago
“Yield to Selected C Topics” We’re about to get right into the DirectX nitty-gritty here, and this assumes that you understand what’s going on in GLOBALS.H – namely, the use of preprocessor statements and the global variables structure G. Since you may not totally understand what’s going on there, I’ve written an article in 03.03 - Selected C Topics that should clear things up for you. In other words, now is a good time to take a quick detour, read that article, and then continue here. DirectX and COM COM stands for Component Object Model. It is a specific way of coding that creates objects that have some cool features like platform independence, language independence, OOP design, global uniqueness, on-the-fly integration and intuitive version control handling. Wow, sounds great huh? Well, we’re not interested in creating COM objects – we’re only interested in using them. As you’ve no doubt guessed, DirectX components are COM objects. Don’t get yourself all confused here. COM objects are nothing more than C++ classes. COM is just a set of rules for designing classes consistently. For instance, all COM objects must have one interface (class) called Iunknown and three methods defined:
  • QueryInterface
  • AddRef
  • Release
A COM object can contain multiple interfaces (read: classes), and each interface can contain multiple methods (read: functions). Here’s a simple example: COM OBJECT : Test (TEST.DLL)
  • IUnknown
    • QueryInterface
    • AddRef
    • Release
  • IMath
    • Add
    • Subtract
    • Multiply
    • Divide
  • IGraphics
    • Line
    • Circle
    • Rectangle
    • Polygon
Of course, this is a simplified example, but the main idea is that the TEST object has three interfaces, and each interface has a few methods. What does this mean to you? Luckily for us, like I’ve said, we don’t need to concern ourselves with COM too much. The general procedure is to call a function, CoCreateInstance() that takes the ID of the object you want, and returns a ‘handle’ to an interface. You can either ask for a particular interface, or you can grab the Iunknown interface which is guaranteed to exist for all COM objects. Then, you can use QueryInterface() to ask for any other interface in the object. Microsoft realizes that there’s a lot of COM overhead involved in getting the objects working for you, so they’ve created wrappers for most of the main DirectX objects. Note that in the game template code there isn’t a call to CoCreateInstance() or QueryInterface() – wrapper functions such as DirectDrawCreateEx(), DirectInputCreateEx() and DirectSoundCreate() do most of the work for you. What you end up with is a pointer to all of that magic COM stuff, and using it is as simple as this: LPTEST pTest; // … pTest->SomeFunction(); pTest->var = 5; In other words, the object is a pointer and is used just as pointers normally are. If the above syntax is new to you, be sure to check 03.03 – Selected C Topics for more information on pointers. Using DirectX Components I should make a final note before getting into the details for the use of each DirectX component. Understanding the syntax of each method call, parameter types, etc. is important, and that can be learned by studying the template code and the online SDK documentation. As far as why certain methods are called and in what order is concerned, well that’s something you just end up figuring out by playing with them all. The online SDK documentation is pretty good at showing you what methods to call and in what order, so (as usual) I highly recommend taking the 5 minute tour. DirectDraw The main COM interface for DirectDraw is called IdirectDraw7. Note that we’re using DirectX 7.0 components in this tutorial, so the first line in DD_Init() asks for this interface:

// Create our DirectDraw object
hRet = DirectDrawCreateEx(NULL, (void**)&G.lpDD, IID_IDirectDraw7, NULL);
if (FAILED(hRet)) return -1;
 
Here’s the prototype for this function:

extern HRESULT WINAPI DirectDrawCreateEx( GUID FAR * lpGuid, LPVOID  *lplpDD, REFIID  iid,IUnknown FAR *pUnkOuter );
 
Let’s go through each parameter, and don’t worry – you’ll get used to these functions fairly quickly. First, they want to know what type of display driver to use. We’ll go with the default, which is our current (active) display driver, and we let DirectDraw know this by passing NULL (folks, now would be a good time to look up DirectDrawCreateEx() in the SDK docs). The second parameter is where we provide our object pointer. When DirectDraw creates the object for us, this will be the pointer given to us to use. This one looks a bit tricky, so let’s dissect it. Looking at the prototype, we’re told that the function requires something of type LPVOID. No problem – this is what LPVOID looks like (ignore the far part): Void * So what they really want looks something like this: (void *)*lplpDD which is actually void **lplpDD They even give you a hand by naming the parameter lplpDD, which reads ‘long pointer long pointer DD’. The reason we’re ignoring the ‘long’ (read: far) part is that all pointers are long these days (4 bytes). Okay, so I admit that this parameter is a bit complicated, but we’ll get through it nonetheless. If you look in GLOBALS.H, here’s what we currently have for our DirectDraw object: LPDIRECTDRAW lpDD; As you can guess, this is a pointer already (we usually use ‘p’ or ‘lp’ to denote ‘pointer’). Well, if the function in question requires a pointer to a pointer to an object, and so far I’ve got a pointer to an object, I’m going to have to get the address of my pointer, like so: &lpDD That says, “the address of the pointer to the object”, or another way of putting it, “a pointer to a pointer to an object”. Hey, we’re just about there! The function asks for “a pointer to a pointer to an object of type void”, and I’ve got “a pointer to a pointer to an object of type IDirectDraw7" (that’s what LPDIRECTDRAW points to). So, I’m finally able to cast the object from my type to theirs: (void **)&G.lpDD Done. I know, I know, this pointer stuff is crazy… check 03.03 – Selected C Topics for a thorough run-through. The next (third) parameter is for the ID of the interface you want DirectDraw to give you. We want IdirectDraw7, so we look it up in the docs and find out that it’s ID is: 0x15e65ec0,0x3b9c,0x11d2,0xb9,0x2f,0x00,0x60,0x97,0x97,0xea,0x5b Yuck. Thankfully, it’s defined in DDRAW.H for us, so we can use its identifier, IID_IdirectDraw7. Just pop it into the function. The last parameter is common to DirectX functions, and is used for some complicated COM aggregation that we’ll never have to worry about. In other words, if a DirectX function wants pUnkOuter, give it NULL. Whew! What did we get for our troubles? We are given a pointer to a newly created DirectDraw object. This object represents the display adapter (video card), so whatever we’d like to do to it, we are given the means to now. As it turns out, our first order of business is to set the cooperation level. This is something that DirectDraw needs for us to do, as it helps DirectDraw manage our video needs with that of other programs (and the OS itself). A quick look at the SDK docs tells us how to fill this function in – we want the whole screen to ourselves, and we don’t want other programs bothering us, so we use the flags DDSCL_EXCLUSIVE and DDSCL_FULLSCREEN together. The first parameter is required by DirectDraw because it needs some way of talking to us, and in Windows that’s done through a window handle. We were given a new window handle when our window was created in WinMain(), and we stored it in our globals just for this purpose. Hopefully, SetDisplayMode() shouldn’t be too difficult to understand. This is where we actually tell the video card to switch to whatever resolution and color depth we want (that it can support, of course). Remember that windowed applications don’t get this honor – only full-screen applications get to choose what they look like. DirectDraw Surfaces So far, we’ve created a DirectDraw object from the IDirectDraw7 interface that manages the video card for us. We’ve used this object to tell DirectDraw what type of environment we’d like to run in, and we’ve used this object to tell the video card to switch to our favorite resolution and color-depth. What we’d have at this point is a full, black screen (we asked for black in WinMain()). Unfortunately, that’s all you’re going to get until you have some way of accessing the video card’s memory, and that’s something else that DirectDraw supplies, by way of DirectDraw surfaces. A DirectDraw surface is a buffer; an area of memory that’s used for images. You can have as many DirectDraw surfaces as you want and in any size you want. One DirectDraw surface that every program needs is called the primary surface – a DirectDraw surface that points to the active display. Once you’ve got that, you are able to access the memory held by the surface, thereby altering the pixels on the display. Usually, we’re going to want more than just a primary display. Do you remember when we looked at animation, and how image after image is prepared and then ‘held up’ to the screen for a moment? DirectDraw allows us to create extra display surfaces for just this purpose. They’re called backbuffers, and you can think of them as ‘extra sheets of paper for drawing screens on’. DirectDraw is great because once we’ve drawn our frame of animation on a back buffer, we call a single function and almost instantly it’s swapped in with the currently visible frame. If you think of DirectDraw surfaces as clear plastic sheets and the active display as an overhead projector, you can quickly visualize the process of drawing and swapping. The function that gives us our surfaces is called CreateSurface(). It’s first parameter is a pointer to a DDSURFACEDESC2 structure, which means that we’ll have to create one and fill in the values first. I’m going to leave it to you to look up the meaning of each parameter in the SDK documentation, but I will tell you what we’re trying to accomplish with this function call – we want to create a primary surface for the active display, and one backbuffer for drawing on. This is called a flipping chain because DirectDraw will ‘flip’ these surfaces onto the display adapter in a cyclic manner. More on this later. When the call to CreateSurface() completes, we have a valid pointer to the primary surface. Usually, the next thing you want to do is also get a pointer to the backbuffer so that you can draw on it. Since the primary surface and the backbuffer are together as a flipping chain, we ask DirectDraw for the backbuffer that is attached to the primary surface by calling GetAttachedSurface(). This function makes us fill in part of a DDSCAPS2 structure in order to let DirectDraw know what we’re after, and we tell it ‘backbuffer, please’. The last two sections of code have to do with loading a bitmap into yet another DirectDraw surface. Note that this isn’t necessary for the template code to work – we don’t use this bitmap (yet), but it’s nice to see the code early as it’s a very common thing in a game. First up is the palette, which is in charge of our color choices. Since we’re in 8-bit palletized color mode, we are required to provide a list of the colors we’d like to use. You can submit this list manually, or you could load the color information from an existing bitmap, which is what we’re going to do. DirectDrawPalette is another DirectX object, and it is created in a utility function called Utils_LoadPalette(). We’ll look at the utility functions in another article, but the end result is that our palette gets created, the colors are read in from a bitmap file, and the palette is attached to the primary surface so that the colors take effect. The last function call again uses a utility function, and it loads the bitmap from disk into a new DirectDraw surface. Keeping with the transparency (clear plastic overhead sheets) analogy, these are smaller pieces that hold images. Later on, we’ll do the equivalent of ‘taping’ these image transparencies onto our backbuffer transparency for viewing on the overhead projector (so to speak). Review We’ve covered a lot in this article, so let’s sum up:
  • COM objects are created from interfaces, which are collections of methods and variables
  • We use DirectDrawCreateEx() to create an object that represents the display adapter, and we use the IID_IdirectDraw7 interface ID to tell DirectDraw what type of object we want
  • Our DirectDraw object is used to set the cooperation level, display resolution and color depth for our game
  • DirectDraw surfaces are used to represent display memory as well as backbuffers that we use for drawing on
  • DirectDraw creates a flipping chain that consists of a primary surface and one or more secondary (backbuffer) surfaces
  • Since we want to draw on the backbuffer, we ask DirectDraw for a pointer to the surface
  • The DirectDrawPalette object is responsible for managing the colors we want to use (when we’re in 8-bit color mode only)
  • An extra DirectDraw surface is created for a sample bitmap image
If you take the time to view INITTERM.CPP while the SDK online documentation is available, you’ll be able to piece things together for yourself. Tear It All Down We’ve look at the initialization process for DirectDraw, and now it’s time to take a quick gander at what’s involved in shutting it all down. When Game_Terminate() is called, a section of that function is responsible for DirectDraw termination, and this involves releasing all surfaces that were created, as well as restoring the display mode (if we’re full-screen and have changed it), and finally releasing the DirectDraw object itself. As you can see in the code, all of these objects have a Release() method for just this purpose. Also note that objects are released in the reverse order from that which they were created in, just to ensure that the cleanup behaves properly. Up Next All that we’ve done so far is get our DirectDraw objects created and initialized – using them is an entirely different matter. You might assume that we need to cover DirectInput and DirectSound next, but that isn’t the case. In my opinion, we shouldn’t bombard ourselves with too much DirectX all at once… in the next article we’re going to investigate DirectDraw further, put it to some good use, and get a lot more comfortable with our template code – especially Game_Main(). Questions? Comments? Reply here!
Advertisement
Thanks Teej thats really cleared alot up for me.

But one questionm why have you called DirectDrawPalette to manage our colors, when you said that only needs to be used in 8-bit color mode and we are in 16-bit color mode?

Was this just an example? or are we really in 8-bit color mode?

And if we are in 8-bit color mode why have you defined SCREEN_BITDEPTH as 16 in globals.h?

Crash,

We Must Move Forwards NOT Backwards, Sideways NOT Forwards And Always Twirling Twirling Towards Success
"We Must Move Forwards NOT Backwards, Sideways NOT Forwards And Always Twirling Twirling Towards Success." - 2000"If You Keep Looking Forward Your Gonna End Up Looking Backwards At Yourself Running Sideways!" - 2001
that''s all fine Teej
but i had to reformat my computer (becuase of a virus)
and i lost the basecode1 zip and when i try to download it it just says site canonot be found!

please help!

Figherdude(A Baldur''s Gate Fan)
Try using this link: http://teejb.webjump.com/basecode1.zip

Edited by - Machaira on May 7, 2001 3:11:40 PM

Former Microsoft XNA and Xbox MVP | Check out my blog for random ramblings on game development

Crash: Good eye! I must have been playing with that value before upping the code to the webpage. Luckily for this simple game template, it doesn''t matter, but you are correct. The correct value for SCREEN_BITDEPTH was supposed to be 8.

Teej

Thanks M it works now
Thanks M it works now
I thought as much Teej, so much so that I''d already changed it to 8 before you posted the topic. No complaint though your doing a great job and teaching me alot.

Thanx,
Crash,



We Must Move Forwards NOT Backwards, Sideways NOT Forwards And Always Twirling Twirling Towards Success
"We Must Move Forwards NOT Backwards, Sideways NOT Forwards And Always Twirling Twirling Towards Success." - 2000"If You Keep Looking Forward Your Gonna End Up Looking Backwards At Yourself Running Sideways!" - 2001
Teej, why are we using 8-bit colour mode?

From looking reading all of the help files, and looking at a few other tutorials, it seems that we would be better off in 16 bit colour mode. That way we don''t need to worry about the pallette.

I may be totally wrong of course, but that is what all the information I have laid my hands on seems to suggest.
As I''ve mentioned, the actual SCREEN_BITDEPTH value doesn''t matter for the original BaseCode1 because nothing is atually drawn onto the display (except some text that doesn''t use the palette anyhow).

While I agree that 8-bit palettized mode is more of a pain, it also happens to be good for 2D games. When I wrote 04.03 - Pixel Manipulation, I wasn''t sure of which color depth to use -- it was a toss-up between palettes (and having to explain them), and 565 16-bit color mode (with some people having 555 video adapters). Anyhow, 16-bit won. This time.

I want to make sure that everyone''s comfortable with all color modes, and then we''ll use different modes for different things, because there are still uses for both regular and palettized colors, but I admit that it''s a debatable topic.

Teej

This topic is closed to new replies.

Advertisement