Upcoming Events
Unite 2010
11/10 - 11/12 @ Montréal, Canada

GDC China
12/5 - 12/7 @ Shanghai, China

Asia Game Show 2010
12/24 - 12/27  

GDC 2011
2/28 - 3/4 @ San Francisco, CA

More events...
Quick Stats
96 people currently visiting GDNet.
2406 articles in the reference section.

Help us fight cancer!
Join SETI Team GDNet!
Link to us Events 4 Gamers
Intel sponsors gamedev.net search:

Contents
 What is DirectX?
 COM
 Setting Up
 DirectDraw
 Cooperation Levels
 Creating Sufaces

 Printable version
 Discuss this article
 in the forums



The Series
 Beginning Windows
 Programming

 Using Resources
 in Win32 Programs

 Tracking Your
 Window/Using GDI

 Introduction
 to DirectX

 Palettes and Pixels
 in DirectDraw

 Bitmapped Graphics
 in DirectDraw

 Developing the
 Game Structure

 Basic Tile Engines
 Adding Characters
 Tips and Tricks

Creating Surfaces

It'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:

typedef struct _DDSURFACEDESC2 { DWORD dwSize; DWORD dwFlags; DWORD dwHeight; DWORD dwWidth; union { LONG lPitch; DWORD dwLinearSize; } DUMMYUNIONNAMEN(1); DWORD dwBackBufferCount; union { DWORD dwMipMapCount; DWORD dwRefreshRate; } DUMMYUNIONNAMEN(2); DWORD dwAlphaBitDepth; DWORD dwReserved; LPVOID lpSurface; DDCOLORKEY ddckCKDestOverlay; DDCOLORKEY ddckCKDestBlt; DDCOLORKEY ddckCKSrcOverlay; DDCOLORKEY ddckCKSrcBlt; DDPIXELFORMAT ddpfPixelFormat; DDSCAPS2 ddsCaps; DWORD dwTextureStage; } DDSURFACEDESC2, FAR *LPDDSURFACEDESC2;

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:

DDSD_ALLIndicates that all input fields are valid.
DDSD_ALPHABITDEPTHIndicates that the dwAlphaBitDepth member is valid.
DDSD_BACKBUFFERCOUNTIndicates that the dwBackBufferCount member is valid.
DDSD_CAPSIndicates that the ddsCaps member is valid.
DDSD_CKDESTBLTIndicates that the ddckDestBlt member is valid.
DDSD_CKDESTOVERLAYIndicates that the ddckDestOverlay member is valid.
DDSD_CKSRCBLTIndicates that the ddckSrcBlt member is valid.
DDSD_CKSRCOVERLAYIndicates that the ddckSrcOverlay member is valid.
DDSD_HEIGHTIndicates that the dwHeight member is valid.
DDSD_LINEARSIZEIndicates that the dwLinearSize member is valid.
DDSD_LPSURFACEIndicates that the lpSurface member is valid.
DDSD_MIPMAPCOUNTIndicates that the dwMipMapCount member is valid.
DDSD_PITCHIndicates that the lPitch member is valid.
DDSD_PIXELFORMATIndicates that the ddpfPixelFormat member is valid.
DDSD_REFRESHRATEIndicates that the dwRefreshRate member is valid.
DDSD_TEXTURESTAGEIndicates that the dwTextureStage member is valid.
DDSD_WIDTHIndicates that the dwWidth member is valid.

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:

typedef struct _DDSCAPS2{ DWORD dwCaps; DWORD dwCaps2; DWORD dwCaps3; DWORD dwCaps4; } DDSCAPS2, FAR* LPDDSCAPS2;

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.

DDSCAPS_BACKBUFFERIndicates that this surface is a back buffer on a surface flipping structure.
DDSCAPS_COMPLEXIndicates a complex surface, which consists of a primary surfaces and one or more attached surfaces, usually for page flipping.
DDSCAPS_FLIPIndicates that this surface is part of a surface flipping structure. The result is that a front buffer along with one or more back buffers are created.
DDSCAPS_FRONTBUFFERIndicates that this surface is the front buffer on a surface flipping structure.
DDSCAPS_LOCALVIDMEMIndicates that this surface exists in true, local video memory rather than non-local video memory. If this flag is specified then DDSCAPS_VIDEOMEMORY must be specified as well. This cannot be used with the DDSCAPS_NONLOCALVIDMEM flag.
DDSCAPS_MODEXIndicates that this surface is a 320×200 or 320×240 Mode X surface.
DDSCAPS_NONLOCALVIDMEMIndicates that this surface exists in nonlocal video memory rather than true, local video memory. If this flag is specified, then DDSCAPS_VIDEOMEMORY flag must be specified as well. This cannot be used with the DDSCAPS_LOCALVIDMEM flag.
DDSCAPS_OFFSCREENPLAINIndicates that this is any plain offscreen surface that is not a texture, back buffer, etc.
DDSCAPS_OWNDCIndicates that this surface will have a device context association for a long period.
DDSCAPS_PRIMARYSURFACEIndicates that this is the primary surface.
DDSCAPS_STANDARDVGAMODEIndicates that this surface is a standard VGA surface rather than a Mode X surface. Cannot be used with the DDSCAPS_MODEX flag.
DDSCAPS_SYSTEMMEMORYIndicates that this surface exists in system memory.
DDSCAPS_TEXTUREIndicates that this surface can be used as a 3D texture, though it is not necessary to use it that way.
DDSCAPS_VIDEOMEMORYIndicates that this surface exists in display memory.

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:

#define INIT_DXSTRUCT(dxs) { ZeroMemory(&dxs, sizeof(dxs)); dds.dwSize = sizeof(dxs); }

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:

HRESULT CreateSurface( LPDDSURFACEDESC2 lpDDSurfaceDesc, LPDIRECTDRAWSURFACE7 FAR *lplpDDSurface, IUnknown FAR *pUnkOuter );

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:

DDSURFACEDESC2 ddsd; // surface description structure LPDIRECTDRAWSURFACE7 lpddsPrimary = NULL; // primary surface // set up primary drawing surface INIT_DXSTRUCT(ddsd); // initialize ddsd ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT; // valid flags ddsd.dwBackBufferCount = 1; // one back buffer ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | // primary surface DDSCAPS_COMPLEX | // back buffer is chained DDSCAPS_FLIP | // allow page flipping DDSCAPS_VIDEOMEMORY; // create in video memory // create primary surface if (FAILED(lpdd7->CreateSurface(&ddsd, &lpddsPrimary, NULL))) { // error-handling code here }

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():

HRESULT GetAttachedSurface( LPDDSCAPS2 lpDDSCaps, LPDIRECTDRAWSURFACE7 FAR *lplpDDAttachedSurface );

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:

LPDIRECTDRAWSURFACE7 lpddsBack = NULL; // back buffer // get the attached surface ddsd.ddsCaps.dwCaps = DDSCAPS_BACKBUFFER; if (FAILED(lpddsPrimary->GetAttachedSurface(&ddsd.ddsCaps, &lpddsBack))) { // error-handling code here }

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:

LPDIRECTDRAWSURFACE7 lpddsOffscreen = NULL; // offscreen buffer // set up offscreen surface INIT_DXSTRUCT(ddsd); // initialize ddsd ddsd.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT; // valid flags ddsd.dwWidth = 400; // set width ddsd.dwHeight = 300; // set height ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | // offscreen buffer DDSCAPS_VIDEOMEMORY; // video memory // create offscreen buffer if (FAILED(lpdd7->CreateSurface(&ddsd, &lpddsOffscreen, NULL))) { // error-handling code here }

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.

Closing

Sorry 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.