|
Is Your Program Just Waiting Around?Sooner or later, there comes a point where we spend every waking hour trying to make the graphics of our application stream faster and smoother. While much of this effort is spent in optimizing our calculations and trimming our content, there is one thing that is sometimes overlooked.... the time we spend waiting around for the screen to refresh so that we can start drawing the next frame. When using page flipping in DirectDraw, all graphic are blitted to a back surface, then the front and back surface are exchanged. To avoid screen tearing, which occurs when the image changes while the screen is being updated, the flipping is delayed until the vertical retrace. Until the surfaces are exchanged, we are unable to draw to the back surface, as the image contained therein is needed to update the display when the time comes. So, we wait... and once the surface is available again, we do our best to optimize getting our blits to the back buffer before the next refresh occurs. So What Can We Do?Well, there is only so much we can do to optimize the blit itself, but there is something we can do about the operations we perform to set up each blit. For every blit, we typically have to calculate the position of an object relative to the current screen, and perform clipping operations. So, why not take care of these operations while we wait. In the pages that follow, we will provide source code for a class that will aid us in preparing to blit the next frame. You can begin as soon as you issue your flip command, calculating the source and destination rectangles for each required blit in the next frame. The parameters for each blit operation are passed to a class function which will store the parameters in a queue. Once the surface is available for blitting, we then only have to call one class function, which will execute the sequence of blits in a tight loop. Defining Our Storage ClassTo start with, we will need to define a storage structure that we can use to store our blit parameters. The class we are providing uses the BltFast() function, which can only be used to write to video memory surfaces. It can easily be adapted, however, to work with the Blt() command. Below is the structure definition: struct blit_region {
} Next we must provide a definition for our class and its members. The class will include a fixed size array of blit_region structures for storing blit parameters. A fixed array is used to avoid the overhead of dynamic allocation, but is limited to the defined size of the array. You can adjust the size of the storage array to meet your needs by adjusting the value of MAX_BLITS. #define MAX_BLITS 100 class CBlitList {
}; The functions we have defined perform the following functions: CBlitList The constructor initializes the queue to a blank list. add_blit The add_blit function is used to add a blit to the queue. The parameters are the same as those utilized by the BltFast() function of DirectDraw. do_blits This function is used to execute the blits that have been written to the queue. It loops through the blits, then resets the queue to an empty list. Defining Our Class FunctionsFirst, to define our constructor - its sole task is to clear our blit counter, in effect setting an empty list: CBlitList::CBlitList(){ // clear blit count num_blits=0; } Next, we create our function to add the blit to the list. Once we have determine that there is space in in the array for another blit, the parameters are transferred to the next array element, and the blit counter is incremented: void CBlitList::add_blit(DWORD x,DWORD y, LPDIRECTDRAWSURFACE surf, RECT *rct,DWORD mode) { // can we add another blit? if (num_blits<MAX_BLITS-1) { // yes, get pointer to next slot in array blit_region *ptr=regions+num_blits; // save the data memcpy(&ptr->rct,rct,sizeof(RECT)); ptr->org_x=x; ptr->org_y=y; ptr->source=surf; ptr->mode=mode; // increment the blit count num_blits++; } } Now that we are able to queue the blits required for a frame, all that is left is to write a function to loop through the sequence and perform them. All blits will be performed to the surface passed to the do_blits function, after which the list counter will be cleared to prepare for the next frame: void CBlitList::do_blits(LPDIRECTDRAWSURFACE surf) { UINT i; // general index counter blit_region *ptr; // region pointer // get pointer to start of blit list ptr=regions; // loop through the blit list and display for (i=0;i<num_blits;i++) { surf->BltFast(ptr->org_x,ptr->org_y,ptr->source,&ptr->rct,ptr->mode); ptr++; } // clear blit count num_blits=0; } Using the Queue in Your ProgramTo use this class in an application, we will need to make a few changes:
We have provided a sample at the end of this page of a sample state engine, which would normally be inserted into the message loop. The loop assumes the existance of 2 functions:
Both of these functions allow for partial completion, by returning TRUE if not finished with the operations required for the next frame. Return FALSE once all operations are complete and ready to be acted upon. The state engine below allows for any order of operation, based on data dependencies and availability of hardware functions. For example, once the blits for a frame are loaded in the queue, the blit will be performed if the hardware is ready to support it. If not, then motion will be calculated, and if the hardware still isn't ready, the parameters for the next blit can be calculated, since the queue does not require access to the surface. The more the hardware lags, the more we are able to pre-calculate for the next frame, allowing us to compensate for loading of resources dynamically. So, without further adieu: BOOL move_flag=TRUE; CBlitList blitList; // are we done blitting the surface and ready to flip? if (flip_flag) { // yes, is the hardware ready to flip if (lpDDSPrimary->GetFlipStatus(DDGFS_CANFLIP)==DD_OK) { // yes, flip the surface set status flags for next blit lpDDSPrimary->Flip(NULL,0); // have we set up a render? } else if (!setup_flag&&blit_flag) { // yes, is the surface ready to accept blitting? if (lpDDSBack->GetBltStatus(DDGBS_CANBLT)==DD_OK) { // yes, perform the blits blitList.do_blits(lpDDSBack); // set flag to allow setup of queue for next frame, and signal for flip setup_flag=TRUE; // are we ready to set up the blits? } else if (!move_flag&&setup_flag) { // yes, render to the queue and clear flag if done setup_flag=SetupRender(&blitList); // if done with setup, enable next motion cycle if (!setup_flag) // do we need to move? } else if (move_flag) { // yes, move and clear flag if done move_flag=MoveObjects();
|
Visitors Since 1/1/2000:
|