Offscreen SurfacesThe 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 SurfacesCreating an offscreen surface is almost identical to creating the primary buffer except for the following:
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 SurfacesNow 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.Setting Up the BlitterTo set up the blitter, you need to do the following:
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 KeysColor 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).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 KeyingSource 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). 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. 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 KeyingDestination 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.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. |