JavaScript EditorFree JavaScript Editor     Ajax Editor 



Main Page
  Previous Section Next Section

Offscreen Surfaces

The whole point of DirectDraw is to take advantage of hardware acceleration. Alas, you can't do that unless you use DirectDraw data structures and objects to hold bitmaps. DirectDraw surfaces are the key to using the blitter. You've already seen how to create a primary surface along with a secondary back buffer to create a page flipping animation chain, but you still need to learn how to create general mxn offscreen surfaces in either system memory or VRAM. With surfaces like this, you can stuff bitmaps into them and then blit the surfaces to the screen using the blitter.

At this point in the game, you can load bitmaps and get the bits out of them, so that problem is solved (minus some cell extraction software). The only piece missing is how to create a general offscreen DirectDraw surface that's neither a primary surface nor a back buffer.

Creating Offscreen Surfaces

Creating an offscreen surface is almost identical to creating the primary buffer except for the following:

  1. You must set the DDSURFACEDESC2.dwFlags to (DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT).

  2. You must set dimensions of the requested surface in DDSURFACEDESC2.dwWidth and DDSURFACEDESC2.dwHeight.

  3. You must set the DDSURFACEDESC2.ddsCaps.dwCaps to DDSCAPS_ OFFSCREENPLAIN | memory_flags, where memory_flags determines where you want the surface to be created. If you set it to DDSCAPS_VIDEOMEMORY, the surface will be created in VRAM (if there's any space). If you set memory_flags equal to DDSCAPS_SYSTEMMEMORY, the surface will be created in system memory, and so the blitter will be almost unused because the bitmap data will have to be transferred over the system bus.

As an example, here's a function that creates any type of surface you request:

LPDIRECTDRAWSURFACE7 DDraw_Create_Surface(int width, int height,
                                          int mem_flags)
{
// this function creates an offscreen plain surface
DDSURFACEDESC2 ddsd;         // working description
LPDIRECTDRAWSURFACE7 lpdds;  // temporary surface

// initialize structure
DDRAW_INIT_STRUCT(ddsd);

// set to access caps, width, and height
ddsd.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT;

// set dimensions of the new bitmap surface
ddsd.dwWidth  =  width;
ddsd.dwHeight =  height;

// set surface to offscreen plain
ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | mem_flags;

// create the surface
if (FAILED(lpdd->CreateSurface(&ddsd,&lpdds,NULL)))
   return(NULL);

// set color key to color 0
DDCOLORKEY color_key; // used to set color key
color_key.dwColorSpaceLowValue  = 0;
color_key.dwColorSpaceHighValue = 0;

// now set the color key for source blitting
lpdds->SetColorKey(DDCKEY_SRCBLT, &color_key);

// return surface
return(lpdds);

} // end DDraw_Create_Surface

Now, if you look at the code, you won't find any mention of the pixel or color depth? Weird huh? Not really… The reason, of course, is that the surfaces you create are always compatible with the primary surface, thus the color depth matches, hence it would be redundant to send that information. Therefore, the above function can be used to create 8, 16, 24, or 32-bit surfaces.

For example, if you wanted to create a 64x64 pixel surface in VRAM, you'd make the following call:

LPDIRECTDRAWSURFACE7 space_ship = NULL; // used to hold surface

// create surface
if (!(space_ship = DDraw_Create_Surface(64,64,DDSCAPS_VIDEOMEMORY)))
    { /* error */ }

TRICK

When you're creating surfaces to hold bitmaps, only create VRAM surfaces of bitmaps that you're going to draw a lot. Moreover, create them in order from largest to smallest.


Now you can do whatever you want with the surface. For example, you might want to lock it so you can copy a bitmap to it. Here's how you would do that:

DDSURFACEDESC2 ddsd; // directdraw surface description

// initialize the structure
DDRAW_INIT_STRUCT();

// lock the surface, check for error in RL (real-life)
space_ship->Lock(NULL, &ddsd,
                 DDLOCK_WAIT | DDLOCK_SURFACEMEMORYPTR,
                 NULL);

// do what you will to ddsd.lpSurface and ddsd.lPitch

// unlock
space_ship->Unlock(NULL);

Then when you're done with the surface (the game is over, whatever) you must release the surface back to DirectDraw as usual with Release():

if (space_ship)
    space_ship->Release();

That's all there is to creating an offscreen surface with DirectDraw! Now let me show you how to blit it to another surface, such as the back buffer or primary surface.

Blitting Offscreen Surfaces

Now that you know how to load bitmaps, create surfaces, and use the blitter, it's time to put it all together and do some real animation! The goal of this section is to load bitmaps that contain the frames of animation for some object (ship, creature, whatever), create a number of small surfaces to hold each frame of animation, and then load the images into each of the surfaces. Once all the surfaces are loaded with bitmap data, you want to blit the surfaces on the screen and animate the object!

Actually, you already know how to accomplish all these steps. About the only thing you haven't done is use the blitter to blit from a surface other than the back buffer to the primary buffer, but there's no difference. Referring to Figure 7.27, you see a number of small surfaces, each with a different frame of animation. In addition, you see both a primary surface and a back buffer surface. The plan is to load all the bitmaps into the small surfaces (the object to animate), use the blitter to blit the small surfaces onto the back buffer, and page flip to see the results. Every so often, you'll blit a different image and move the destination of the blit slightly to animate and move the object.

Figure 7.27. Blitting offscreen surfaces to the back buffer.

graphics/07fig27.gif

Setting Up the Blitter

To set up the blitter, you need to do the following:

  1. Set up the source RECT to blit from. This will be the small surface (8x8, 16x16, 64x64, etc.) containing the image of interest. Usually the coordinates will be (0,0) to (width–1, height–1)—that is, the whole surface.

  2. Set up the destination RECT, which will usually be the back buffer. This part is a little tricky because you want to copy the source image at some location (x,y), so the RECT should be set with this in mind: (x,y) to (x+width-1,y+height-1).

  3. Make a call to IDIRECTDRAWSURFACE7::Blt() with the proper parameters—which you'll see shortly.

NOTE

If you make the destination RECT larger or smaller than the source RECT (the image), the blitter will scale the image appropriately to fit—this is the basis of 2.5D sprite-scaled games.


There's one problem that I must address before you see the call to the Blt() function—color keying.

Color Keys

Color keys are a bit hard to explain, probably because of their naming convention under DirectDraw. Let me give it a try. When you're performing bitmap operations, in most cases you're blitting bitmap objects that are contained in rectangular cells. However, when you draw the bitmap of a little creature, you usually don't want to copy the contents of the entire cell. You want only to copy the bits that relate to the creature, so you need to select a color (or colors) as transparent. Figure 7.28 shows a transparent blit vs. a nontransparent blit. I've discussed this before, and you've even implemented it in your software blitter for the exercise.

Figure 7.28. Transparent blit (top) versus nontransparent blit (bottom).

graphics/07fig28.gif

DirectDraw has a much more sophisticated color keying system than just selecting a simple transparent color. It can do much more than just perform blits with basic transparency. Let's take a quick look at the different types of color keys, and then I'll show you how to set up color keying for the type of operation you're interested in.

Source Color Keying

Source color keying is the color keying that you want to use and is the easiest to understand. Basically, you select a single color index (in 256-color modes) or a range of RGB color values that will act as transparent for your source image. Then, when you blit the source to the destination, the pixels that have the same value as the transparent color(s) will not be copied. Figure 7.14 shows this process. You can set the color key for a surface while creating the surface, or do it after the fact with IDIRECTDRAWSURFACE7::SetColorKey(). I'll show you both methods in a moment, but first look at the data structure that holds the color key. It's called DDCOLORKEY:

typedef struct _DDCOLORKEY
        {
        DWORD dwColorSpaceLowValue;  // low value (inclusive)
        DWORD dwColorSpaceHighValue; // high value (inclusive)
        } DDCOLORKEY,FAR* LPDDCOLORKEY;

The low- and high-color key values are a bit tricky, so listen up. If you're using 8-bit surfaces, the values should be color indices. If you're using 16-, 24-, or 32-bit surfaces, you actually use the RGB-encoded WORDs for the particular surface format as the values to store in the low- and high-color keywords. For example, let's say you're running in an 8-bit mode and you want color index 0 to be transparent. Here's how you would set up the color key:

DDCOLORKEY key;

key.dwColorSpaceLowValue  = 0;
key.dwColorSpaceHighValue = 0;

And if you wanted the range from color index 10–20 (inclusive) to be transparent:

key.dwColorSpaceLowValue  = 10;
key.dwColorSpaceHighValue = 20;

Next, let's say you're running in a 16-bit 5.6.5 mode and want pure blue to be transparent:

key.dwColorSpaceLowValue  = __RGB16BIT565(0,0,32);
key.dwColorSpaceHighValue = __RGB16BIT565(0,0,32);

Similarly, let's say you want the range of colors from black to half-intensity red to be transparent in the same 16-bit mode:

key.dwColorSpaceLowValue  = _RGB16BIT565(0,0,0);
key.dwColorSpaceHighValue = _RGB16BIT565(16,0,0);

Get the idea? Now let's take a look at how to set a DirectDraw surface's color key during creation. All you need to do is add the flag DDSD_CKSRCBLT (other valid settings shown in Table 7.5) to the dwFlags WORD of the surface descriptor, and then assign the low- and high-color keywords in the DDSURFACEDESC2.ddckCKSrcBlt, member.dwColorSpaceLowValue, and DDSURFACEDESC2.ddckCKSrcBlt. dwColorSpaceHighValue fields (there are also members for destination and overlay color key information).

Table 7.5. Color Key Surface Flags
Value Description
DDSD_CKSRCBLT Indicates that the ddckCKSrcBlt member of the DDSURFACEDESC2 is valid and contains color key information for source color keying.
DDSD_CKDESTBLT Indicates that the ddckCKDestBlt member of the DDSURFACEDESC2 is valid and contains color key information for destination color keying.
DDSD_CKDESTOVERLAY Indicates that the ddckCKDestOverlay member of the DDSURFACEDESC2 is valid and contains color key information for destination overlay color keying.
DDSD_CKSRCBLT Indicates that the ddckCKSrcBlt member of the DSURFACEDESC2 is valid and contains color key information for source color keying.
DDSD_CKSRCOVERLAY Indicates that the ddckCKSrcOverlay member of the DSURFACEDESC2 is valid and contains color key information for source overlay color keying.

Here's an example:

DDSURFACEDESC2 ddsd;         // working description
LPDIRECTDRAWSURFACE7 lpdds;  // temporary surface

// initialize structure
DDRAW_INIT_STRUCT(ddsd);

// set to access caps, width, and height
ddsd.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT | DDSD_CKSRCBLT;

// set dimensions of the new bitmap surface
ddsd.dwWidth  =  width;
ddsd.dwHeight =  height;

// set surface to offscreen plain
ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | mem_flags;

// set the color key fields
ddsd.ddckCKSrcBlt.dwColorSpaceLowValue  = low_color;
ddsd.ddckCKSrcBlt.dwColorSpaceHighValue = high_color;

// create the surface
if (FAILED(lpdd->CreateSurface(&ddsd,&lpdds,NULL)))
   return(NULL);

And once you've created a surface with or without a color key, you can always set it after the fact using the function IDIRECTDRAWSURFACE7:SetColorKey():

HRESULT SetColorKey(DWORD dwFlags,
                    LPDDCOLORKEY lpDDColorKey);

The valid flags are listed in Table 7.6.

Table 7.6. Valid Flags for SetColorKey()
Value Description
DDCKEY_COLORSPACE Indicates that the structure contains a color space. You must set this if you're setting a range of colors.
DDCKEY_SRCBLT Indicates that the structure specifies a color key or color space to be used as a source color key for blit operations.
DDCKEY_DESTBLT Indicates that the structure specifies a color key or color space to be used as a destination color key for blit operations.
DDCKEY_DESTOVERLAY Set if the structure specifies a color key or color space to be used as a destination color key for overlay operations. (Advanced)
DDCKEY_SRCOVERLAY Set if the structure specifies a color key or color space to be used as a source color key for overlay operations. (Advanced)

Here's an example:

// assume lpdds points to a valid surface

// set color key
DDCOLORKEY color_key; // used to set color key
color_key.dwColorSpaceLowValue  = low_value;
color_key.dwColorSpaceHighValue = high_value;

// now set the color key for source blitting, notice
// the use of DDCKEY_SRCBLT
lpdds->SetColorKey(DDCKEY_SRCBLT, &color_key);

NOTE

If you set a range of colors for the source key, you must add the flag DDCKEY_COLORSPACE in the call to SetColorKey(). For example:

lpdds->SetColorKey(DDCKEY_SRCBLT | DDCKEY_COLORSPACE,  &color_key);

Otherwise, DirectDraw will collapse the range to one value.


Destination Color Keying

Destination color keying is great in theory, but it never seems to get used. The basic concept of destination color keying is shown in Figure 7.29. The idea is as follows: You set a color or range of colors in the destination surface that can be blitted to. In essence, you're creating a mask of sorts. This way you can simulate windows, fences, etc.

Figure 7.29. Destination color keying.

graphics/07fig29.gif

You can set a destination color key the exact same way you did for a source. Just change a couple of the flags. For example, to set a destination color key during creation of a surface, you set the exchange DDSD_CKSRCBLT for DDSD_CKDESTBLT when setting up the DDRAWSURFACEDESC2.dwFlags, and of course the key values will go into ddsd.ddckCKDestBlt rather than ddsd.ddckCKSrcBlt:

// set the color key fields
ddsd.ddckCKDestBlt.dwColorSpaceLowValue  = low_color;
ddsd.ddckCKDestBlt.dwColorSpaceHighValue = high_color;

If you want to set a destination color key after a surface is created, you do everything the same except during the call to SetColorKey(), where you must switch the DDCKEY_SRCBLT flag to DDCKEY_DESTBLT, like this:

lpdds->SetColorKey(DDCKEY_DESTBLT, &color_key);

WARNING

Destination color keying is currently only available in the HAL (Hardware Abstraction Layer), not the HEL. Hence, if there isn't hardware support for destination color keying, it won't work. This will probably change for future versions of DirectX.


Finally, there are two more types of keys: source overlays and destination overlays. They're useless for your purposes, but they come in handy for video processing. If you're interested, take a look at the DirectX SDK.

Using the Blitter (Finally!)

Now that you have the preliminaries out of the way, blitting an offscreen surface to any other surface is a snap. Here's how. Assume that you've created a 64x64-pixel 8-bit color surface image with color index 0 as the transparent color, or something like this:

DDSURFACEDESC2 ddsd;               // working description
LPDIRECTDRAWSURFACE7 lpdds_image;  // temporary surface
// initialize structure
DDRAW_INIT_STRUCT(ddsd);

// set to access caps, width, and height
ddsd.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT | DDSD_CKSRCBLT;

// set dimensions of the new bitmap surface
ddsd.dwWidth  =  64;
ddsd.dwHeight =  64;

// set surface to offscreen plain
ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | mem_flags;

// set the color key fields
ddsd.ddckCKSrcBlt.dwColorSpaceLowValue  = 0;
ddsd.ddckCKSrcBlt.dwColorSpaceHighValue = 0;

// create the surface
if (FAILED(lpdd->CreateSurface(&ddsd,&lpdds_image,NULL)))
   return(NULL);

Next, imagine that you have both a primary surface, lpddsprimary, and a back buffer surface, lpddsback, and that you want to blit the surface lpdds_image to the back buffer at location (x,y) with the source color key you set. Here's how:

// fill in the destination rect
dest_rect.left   = x;
dest_rect.top    = x;
dest_rect.right  = x+64-1;
dest_rect.bottom = y+64-1;

// fill in the source rect
source_rect.left    = 0;
source_rect.top     = 0;
source_rect.right   = 64-1;
source_rect.bottom  = 64-1;

// blt to destination surface
if (FAILED(lpddsback->Blt(&dest_rect, lpdds_image,
          &source_rect,
          (DDBLT_WAIT | DDBLT_KEYSRC),
          NULL)))
    return(0);

That's it, baby! Notice the flag DDBLT_KEYSRC. You must have this in the blit call, or else the color key won't work even though there is one defined by the surface.

WARNING

When you're blitting remember to watch out for clipping. Not setting a clipping region to the destination surface and blitting beyond it would be very bad. But all you have to do is make a call to your function DDraw_Attach_Clipper() and set a single clipping RECT that's identical to the bounds of the screen.


At long last, you're ready for a reasonably cool demo. Figure 7.30 is a screen shot of DEMO7_13.CPP|EXE. Looks cool, huh? What I've decided to do is add a little game programming so you can get more out of this demo than just some moving bitmaps. Basically, the demo loads in a large background bitmap and a number of frames of animation for an alien. The large background is copied to the back buffer each frame, along with a number of replicated and animated copies of the alien (which are surfaces). The aliens are then animated and moved at various velocities. See if you can add a player that's controlled by the keyboard to the demo!

Figure 7.30. DEMO7 13.EXE in action.

graphics/07fig31.gif

      Previous Section Next Section
    



    JavaScript EditorAjax Editor     JavaScript Editor