Creating SurfacesIt's time for something that requires a little more than just a function call! Creating surfaces isn't too tough. Actually, it is accomplished with a single function call, but first you need to fill out a structure that describes the surface you want to create. Before I show you this, I just want to say that you don't have to fill out the whole thing, so never fear. :) Here it is, the DDSURFACEDESC2:
DirectX has lots of big structures. This one is pretty big, and what's worse, it's got all sorts of structures nested inside it! Anyway, let's take a look at this monster and see what it's got to offer. I'm only going to cover the important ones, so I don't grow old writing this. DWORD dwSize: Any DirectX structure has a dwSize member, which must be set to the size of the structure. It's there so that functions receiving a pointer to one of these structures can determine its size. DWORD dwFlags: Great, more flags. :) These flags are used to tell a receiving function which fields are important. Any field containing valid information must have its corresponding flag specified in dwFlags, and they can all be combined with | as usual. Here's the list:
DWORD dwHeight, dwWidth: These are the dimensions of the surface in pixels. LONG lPitch: This one needs to be explained a bit. The lPitch member represents the number of bytes in each display line. You'd think this would be obvious. For example, in 640x480x16, there are 640 pixels in each line, and each one requires 2 bytes for color information, so the pitch (also called the "stride") should be 1280 bytes, right? Well, on some video cards, it will be greater than 1280. The extra memory on each line doesn't hold any graphical data, but sometimes it's there because the video card can't create a perfectly linear memory mode for the display mode. This will happen on a very small percentage of video cards, but you need to take it into account. LPVOID lpSurface: This is a pointer to the memory represented by the surface. No matter what display mode you're using, DirectDraw creates a linear addressing mode you can use to manipulate the pixels of the surface. In order to do this, you must lock the surface... but we're getting ahead of ourselves! DWORD dwBackBufferCount: This is the number of back buffers that will be chained to the primary surface, either for double- or triple-buffering, or for a page flipping chain. Again, more on this later. DWORD ddckDestBlt, ddckSrcBlt: These are color keys, which control which color pixels can be written to, or written, respectively. This is used to set transparent colors in bitmaps, as we'll see in a future article. DDPIXELFORMAT ddpfPixelFormat: This structure contains a number of descriptors for the pixel format of the display mode. We'll be covering this next time as well, so I won't show you this giant structure right now. DDSCAPS2 ddsCaps: This is the last important one, and it's another structure full of control flags. Thankfully, the structure a small one, and only one of its members is important right now. Take a look:
The only important one is dwCaps. The third and fourth members aren't even used at all; they're just there for future expansion. Anyway, dwCaps can take the following values, logically combined with the | operator. This is only a partial list, leaving out some that are advanced or not currently implemented.
All right, we're finally done looking at structures. Now we're just about ready to create a surface. The first step is to fill out a DDSURFACEDESC2 structure. Microsoft recommends that before using a structure which you won't be filling out completely, you should zero it out to initialize it. To this end, I usually use a macro something like this:
This can be used for any DirectX structure, since they all have the dwSize member, so it's pretty convenient. If you've never seen ZeroMemory() before, it's just a macro that expands into a memset() call. It's #defined in the Windows headers, so you don't need to #include anything new to use it. After initializing the structure, the minimum you'll need to fill out depends on the surface. For primary surfaces, you'll just need ddsCaps and dwBackBufferCount. For offscreen buffers, you'll also need dwHeight and dwWidth, but not dwBackBufferCount. For some surfaces you may also want to use the color keys, but we're not that far yet. After you've filled out the structure, you make a call to IDirectDraw7::CreateSurface(), which looks like this:
The parameters, you can probably figure out. But here they are, since we're just getting used to all this crazy DirectX stuff: LPDDSURFACEDESC2 lpDDSurfaceDesc: This is a pointer to the DDSURFACEDESC2 you'll need to fill out to describe the surface. LPDIRECTDRAWSURFACE7 FAR *lplpDDSurface: Since a surface is defined by an interface pointer, you need to create a variable of type LPDIRECTDRAWSURFACE7 to hold the pointer, and then pass its address here. IUnknown FAR *pUnkOuter: Starting to see a pattern? Whenever you see something called pUnkOuter, it's for advanced COM stuff we don't want to mess around with. :) Set it to NULL. Let's run through an example. Suppose that for our program, we want a primary surface with one back buffer attached, and one offscreen buffer to use for bitmap storage. Assuming we already have our IDirectDraw7 interface pointer, the following code would create the primary surface:
Now, when you specify the DDSCAPS_COMPLEX flag, the call to CreateSurface() actually creates the front buffer along with any back buffers it may have. Since we specified that there was to be one back buffer, all we need to do is get the pointer to it. This is done by calling IDirectDrawSurface7::GetAttachedSurface():
The parameters are pretty easy to deal with: LPDDSCAPS2 lpDDSCaps: A pointer to a DDSCAPS2 structure which describes the attached surface. You can use the corresponding member of our DDSURFACEDESC2 structure. LPDIRECTDRAWSURFACE7 FAR *lplpDDAttachedSurface: This is the address of the interface pointer that will represent the attached surface. Simply declare a pointer and pass its address. With that relatively painless function, we can get the back buffer which was created along with the primary surface. The following code does the job:
Are you starting to get the hang of this stuff? If you're still having trouble remembering all these steps, just give it time. And nobody remembers every member of every structure there is, so don't worry about those huge lists of members and flags. That's why you have your MSDN Library CD. :) Anyway, the last step is to create the offscreen buffer. Suppose we wanted it to be 400 pixels wide and 300 pixels high. We would go about creating it like this:
And we're all set! Now we've got all of our surfaces created and we're ready to do graphics. Of course, the only problem is that this article has gone pretty long already, so we're going to have to stop here. You can now create a working DirectX program, even though it won't do anything except set up your surfaces. Remember that when you're done with the DirectDraw interface, and with all your surfaces, you must release them all, and it's best to release in the opposite order of creation. ClosingSorry to cut this off before we got to any graphics, but if I were to start talking about graphics, the plain text version of this article would go over 100KB, to say nothing of the HTML-ified version. :) There's so much to explain when it comes to working with graphics, so I think I'm going to split it up over the next two articles. The next installment will cover pixel-based graphics and setting up palettized modes, and the article after that will discuss bitmaps and clipping. Until then, if you have any questions, send me an E-mail, or catch me on ICQ at UIN #53210499. See you next time! Copyright © 2000 by Joseph D. Farrell. All rights reserved. |