330 likes | 346 Views
Learn how to implement page flipping, clear back buffer, render scenes, and use the blitter function for efficient bitmap handling in game development using DirectDraw.
E N D
DirectDraw and BitmapsPart 2 CIS 487/587 Bruce R. Maxim UM-Dearborn
Page Flipping • Clear back buffer • Render scene to back buffer surface • Flip primary surface with back buffer surface • Lock to frame rate (e.g. 30 fps) • Repeat step 1
Creating Primary Surface with Back Buffer • Add DDSD_BACKBUFFERCOUNT to the dwFlags field so DirectDraw can check number surfaces at creation • Add DFDSCAPS_COMPLEX and DDSCAPS_FLIP to capabilities word of DDSURFACE2 contained in ddsCaps.dwCaps filed • Create primary surface as usual and request attached buffer using GetAttachedSurface( )
Inside Game_Init( ) // enable valid fields ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT; // set the backbuffer count field for double buffering ddsd.dwBackBufferCount = 1; // request a complex, flippable ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_COMPLEX | DDSCAPS_FLIP; // create the primary surface if (FAILED(lpdd->CreateSurface(&ddsd, &lpddsprimary, NULL))) return(0); // now query for attached surface from the primary surface // this line is needed by the call ddsd.ddsCaps.dwCaps = DDSCAPS_BACKBUFFER; // get the attached back buffer surface if (FAILED(lpddsprimary->GetAttachedSurface(&ddsd.ddsCaps, &lpddsback))) return(0);
Inside Game_Shutdown( ) // first the palette if (lpddpal) { lpddpal->Release(); lpddpal = NULL; } // end if // now the back buffer surface if (lpddsback) { lpddsback->Release(); lpddsback = NULL; } // end if // now the primary surface if (lpddsprimary) { lpddsprimary->Release(); lpddsprimary = NULL; } // end if // then the DirectDrawInterface
Inside Game_Main( ) // draw the next frame into the back buffer, notice that we // must use the lpitch since it's a surface and may not be linear // plot 5000 random pixels for (int index=0; index < 5000; index++) { int x = rand()%SCREEN_WIDTH; int y = rand()%SCREEN_HEIGHT; UCHAR col = rand()%256; back_buffer[x+y*ddsd.lPitch] = col; } // end for index // unlock the back buffer if (FAILED(lpddsback->Unlock(NULL))) return(0); // perform the flip (both primary and back buffer must be unlocked while (FAILED(lpddsprimary->Flip(NULL, DDFLIP_WAIT)));
Using the Blitter • The blitter can be used to paste bit images from off screen surfaces to the primary surface • The function Blt( ) does blitting using DirectDraw clippers • The function BltFast( ) does not do clipping and runs faster
Blitting to do Simple 8 Bit Fills • Place the color index or RGB color you want to use to fill the surface in the dwFillColor field of a DDBLTFX struct • Define a RECT in the area you want to fill on the destination surface • Call Blt( ) from destination surface interface pointer using control falgs DDBT_COLORFILL | DDBLT_WAIT
Inside Game_Init( ) // create IDirectDraw interface 7.0 object // set cooperation to full screen // set display mode // clear ddsd and set size DDRAW_INIT_STRUCT(ddsd); // enable valid fields ddsd.dwFlags = DDSD_CAPS; // request a complex, flippable ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE; // create the primary surface
Inside Game_Main( ) DDBLTFX ddbltfx; // the blitter fx structure RECT dest_rect; // used to hold the destination RECT // first initialize the DDBLTFX structure DDRAW_INIT_STRUCT(ddbltfx); // now set the color word info to the color we desire // in this case, we are assuming an 8-bit mode, hence, // well use a color index from 0-255 ddbltfx.dwFillColor = _RGB16BIT565(rand()%256, rand()%256, rand()%256);
Inside Game_Main( ) // now set up the RECT structure to fill the region from // (x1,y1) to (x2,y2) on the destination surface dest_rect.left = x1; dest_rect.top = y1; dest_rect.right = x2; dest_rect.bottom = y2; // make the blitter call if (FAILED(lpddsprimary->Blt(&dest_rect, // pointer to dest RECT NULL, // pointer to source surface NULL, // pointer to source RECT DDBLT_COLORFILL | DDBLT_WAIT, // do color fill wait if you have to &ddbltfx))) // pointer to DDBLTFX info return(0);
Inside Game_Main( ) • Please note, in this example the call to Blt( ) has NULL pointers to both the surface and the source rectangle • The reason for this is that the color fills are handled by the blitter hardware support (or emulation if needed)
Copying Bitmaps Between Surfaces • When using the Blt( ) function you are sending both a source and destination rectangle to use in performing the blit • Blitting when the source and destination surfaces are different is the basis for most sprite engines • A sprite is a bitmap the seems to move on the screen
Game_Main( ) RECT source_rect, // used to hold the destination RECT dest_rect; // used to hold the destination RECT // now set up the RECT structure to fill the region from // (x1,y1) to (x2,y2) on the source surface source_rect.left = x1; source_rect.top = y1; source_rect.right = x2; source_rect.bottom = y2; // now set up the RECT structure to fill the region from // (x3,y3) to (x4,y4) on the destination surface dest_rect.left = x3; dest_rect.top = y3; dest_rect.right = x4; dest_rect.bottom = y4;
Game_Main( ) // make the blitter call if (FAILED(lpddsprimary->Blt(&dest_rect, // pointer to dest RECT lpddsback, // source surface &source_rect,// pointer source RECT DDBLT_WAIT, // control flags NULL))) // pointer to DDBLTFX info return(0);
Clipping • Suppose you want to clip a pixel with coordinates (x,y) to a viewport from (x1,y1) to (x2,y2) void Plot_Pixel_Clip8(int x, int y, UCHAR color, UCHAR *video_buffer) { // test for coordinates in range if (x >= x1 && x <<= x2 && y >= y && y <= y2) video_buffer[x + y * 640] = color; }
Clipping Bitmaps the Hardway • Method 1 (image space clipping) • Clip each pixel of the bitmap individually as each is generated (simple but slow) • Method 2 (object space clipping) • Clip the bounding rectangle of the bitmap to the viewport and then only draw the pixels that are in range (complex, but very fast)
Blit_Clipped( ) void Blit_Clipped(int x, int y, // position to draw bitmap int width, int height, // size of bitmap in pixels UCHAR *bitmap, // pointer to bitmap data UCHAR *video_buffer, // pointer to video buffer surface int mempitch) // video pitch per line { // this function blits and clips the image sent in bitmap to the // destination surface pointed to by video_buffer // the function assumes a 640x480x8 mode // first do trivial rejections of bitmap, is it totally invisible? if ((x >= SCREEN_WIDTH) || (y>= SCREEN_HEIGHT) || ((x + width) <= 0) || ((y + height) <= 0)) return;
Blit_Clipped( ) // clip source rectangle // pre-compute the bounding rect to make life easy int x1 = x; int y1 = y; int x2 = x1 + width - 1; int y2 = y1 + height -1; // upper left hand corner first if (x1 < 0) x1 = 0; if (y1 < 0) y1 = 0; // now lower left hand corner if (x2 >= SCREEN_WIDTH) x2 = SCREEN_WIDTH-1; if (y2 >= SCREEN_HEIGHT) y2 = SCREEN_HEIGHT-1;
Blit_Clipped( ) // now we know to draw only the portions of the bitmap // from (x1,y1) to (x2,y2) // compute offsets into bitmap on x,y axes, we need this // to compute starting point // to rasterize from int x_off = x1 - x; int y_off = y1 - y; // compute number of columns and rows to blit int dx = x2 - x1 + 1; int dy = y2 - y1 + 1; // compute starting address in video_buffer video_buffer += (x1 + y1*mempitch); // compute starting address in bitmap to scan data from bitmap += (x_off + y_off*width);
Blit_Clipped( ) // bitmap is pointing to the first pixel in bitmap that needs to // be blitted, and video_buffer is pointing to memory location on // the destinationbuffer to put it, so now enter rasterizer loop UCHAR pixel; // used to read/write pixels for (int index_y = 0; index_y < dy; index_y++) { // inner loop, where the action takes place for (int index_x = 0; index_x < dx; index_x++) { // read pixel from source bitmap, test transparency and plot if ((pixel = bitmap[index_x])) video_buffer[index_x] = pixel; } // end for index_x // advance pointers video_buffer += mempitch; // bytes per scanline bitmap += width; // bytes per bitmap row } // end for index_y } // end Blit_Clipped
Creating Happy Face // the happy face structure typedef struct HAPPY_FACE_TYP { int x,y; // position of happy face int xv, yv; // velocity of happy face } HAPPY_FACE, *HAPPY_FACE_PTR; // a low tech bitmap that uses palette entry 1 for the color :) UCHAR happy_bitmap[64] = {0,0,0,0,0,0,0,0, 0,0,1,1,1,1,0,0, 0,1,0,1,1,0,1,0, 0,1,1,1,1,1,1,0, 0,1,0,1,1,0,1,0, 0,1,1,0,0,1,1,0, 0,0,1,1,1,1,0,0, 0,0,0,0,0,0,0,0}; HAPPY_FACE happy_faces[100]; // this holds all the happy faces
Game_Init( ) // create IDirectDraw interface 7.0 object and test for error // set cooperation to full screen // set display mode // clear ddsd and set size // set the backbuffer count field to 1 // create the primary surface (complex and flipable) // now query for attached surface from the primary surface // get the attached back buffer surface // build up the palette data array and create the palette object // finally attach the palette to the primary surface // initialize all the happy faces for (int face = 0; face < 100; face++) { happy_faces[face].x = rand()%SCREEN_WIDTH; happy_faces[face].y = rand()%SCREEN_HEIGHT; happy_faces[face].xv = -2 + rand()%5; happy_faces[face].yv = -2 + rand()%5; } // end for face
Game_Main( ) DDBLTFX ddbltfx; // the blitter fx structure // now set the color word info to the color we desire ddbltfx.dwFillColor = 0; // make the blitter call if (FAILED(lpddsback->Blt(NULL, // pointer dest RECT NULL, // pointer to source surface NULL, // pointer to source RECT DDBLT_COLORFILL | DDBLT_WAIT, // do a color fill and wait if you have to &ddbltfx))) // pointer to DDBLTFX info return(0); // initialize ddsd DDRAW_INIT_STRUCT(ddsd); // lock the back buffer surface if (FAILED(lpddsback->Lock(NULL,&ddsd, DDLOCK_WAIT | DDLOCK_SURFACEMEMORYPTR, NULL))) return(0);
Game_Main( ) // draw all the happy faces for (int face=0; face < 100; face++) { Blit_Clipped(happy_faces[face].x, happy_faces[face].y, 8,8, happy_bitmap, (UCHAR *)ddsd.lpSurface, ddsd.lPitch); } // end for
Game_Main( ) // move all happy faces for (face=0; face < 100; face++) { // move happy_faces[face].x+=happy_faces[face].xv; happy_faces[face].y+=happy_faces[face].yv; // check for off screen, if so wrap if (happy_faces[face].x > SCREEN_WIDTH) happy_faces[face].x = -8; else if (happy_faces[face].x < -8) happy_faces[face].x = SCREEN_WIDTH; if (happy_faces[face].y > SCREEN_HEIGHT) happy_faces[face].y = -8; else if (happy_faces[face].y < -8) happy_faces[face].y = SCREEN_HEIGHT; } // end face
DirectDraw Clipping • Create a DirectDraw clipper object • Create a clipping list • Send clipping list to clipper using SetClipList( ) • Attach clipper to window and/or surface using SetClipper( )
Creating Clipper Object LPDIRECTDRAWCLIPPER lpddclipper; // pointer to the // newly created dd clipper LPRGNDATA region_data; // pointer to the region // data that contains // the header and clip list // first create the direct draw clipper if (FAILED(lpdd->CreateClipper(0,&lpddclipper,NULL))) return(NULL);
Creating Clipping List // first allocate memory for region data region_data = (LPRGNDATA)malloc(sizeof(RGNDATAHEADER)+num_rects*sizeof(RECT)); // now copy the rects into region data memcpy(region_data->Buffer, clip_list, sizeof(RECT)*num_rects); // set up fields of header region_data->rdh.dwSize = sizeof(RGNDATAHEADER); region_data->rdh.iType = RDH_RECTANGLES; region_data->rdh.nCount = num_rects; region_data->rdh.nRgnSize = num_rects*sizeof(RECT); region_data->rdh.rcBound.left = 64000; region_data->rdh.rcBound.top = 64000; region_data->rdh.rcBound.right = -64000; region_data->rdh.rcBound.bottom = -64000;
Creating Clipping List // find bounds of all clipping regions for (index=0; index<num_rects; index++) { // test if the next rectangle unioned with current bound is larger if (clip_list[index].left < region_data->rdh.rcBound.left) region_data->rdh.rcBound.left = clip_list[index].left; if (clip_list[index].right > region_data->rdh.rcBound.right) region_data->rdh.rcBound.right = clip_list[index].right; if (clip_list[index].top < region_data->rdh.rcBound.top) region_data->rdh.rcBound.top = clip_list[index].top; if (clip_list[index].bottom > region_data->rdh.rcBound.bottom) region_data->rdh.rcBound.bottom = clip_list[index].bottom; } // end for index
Set the Clipping List // we have computed the bounding rectangle region and set up the data // now let's set the clipping list if (FAILED(lpddclipper->SetClipList(region_data, 0))) { // release memory and return error free(region_data); return(NULL); } // end if
Attach Clipper and Clean Up // we have computed the bounding rectangle region and set up the data // now attach the clipper to the surface if (FAILED(lpdds->SetClipper(lpddclipper))) { // release memory and return error free(region_data); return(NULL); } // end if // all is well, so release memory and send back the pointer to // the new clipper free(region_data);