330 likes | 340 Views
DirectDraw and Bitmaps Part 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.
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);