Using a Blit Queue

Home | Up | Search | X-Zone News | Services | Book Support | Links | Feedback | Smalltalk MT | The Scrapyard | FAQ | Technical Articles

 

Using a Blit Queue to Optimize DirectDraw

Written by Robert Dunlop
Microsoft DirectX MVP


Related Articles of Interest:

Rendering Full Screen Images from Textures
A Simple Blit Function for DirectX 8

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 Class

To 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 {

RECT rct;
UINT org_x;
UINT org_y;
LPDIRECTDRAWSURFACE source;
DWORD mode;

}

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 {

public:

CBlitList();
void add_blit(DWORD x,DWORD y,
LPDIRECTDRAWSURFACE surf,
RECT *rct,DWORD mode);
void do_blits(LPDIRECTDRAWSURFACE surf);

private:

UINT num_blits;
blit_region regions[MAX_BLITS];

};

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 Functions

First, 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 Program

To use this class in an application, we will need to make a few changes:

bullet

We will call CBlitList::add_blit() in place of IDirectDrawSurface::BltFast()

bullet

We will monitor the flip status to see when we are ready to flip, rather than calling Flip() with the DDFLIP_WAIT flag set.

bullet

We will monitor the blit status to determine when the batch can be executed, once the flip has actually been executed.

bullet

To make the best use of this class, we should break up the frame operations into pass through functions that can be called, perform a short operation, and return whether there is more to be done to complete the frame.  This allows us to properly monitor the blit and flip status without missing a frame, as well as providing more timely message handling.

bullet

We will need to generate some logic in our message loop that can keep track of various operations, and allow them to run out of sequence to allow the processor to make use of slack time in any portion of the frame cycle.

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:

MoveObjects Performs all non DirectDraw frame operations, such as object motion, scoring, user input, and sound.
SetupRender Receives a pointer to a CBlitList class instance and sets up the blit parameters for all blits in the frame.

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;
BOOL setup_flag=TRUE;
BOOL blit_flag=TRUE;
BOOL flip_flag=FALSE;

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);
       flip_flag=FALSE;
       blit_flag=TRUE;
    }

// 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;
       flip_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)
        move_flag=TRUE;

// do we need to move?

} else if (move_flag) {

// yes, move and clear flag if done

      move_flag=MoveObjects();
}

Related Articles of Interest:

Rendering Full Screen Images from Textures
A Simple Blit Function for DirectX 8

This site, created by DirectX MVP Robert Dunlop and aided by the work of other volunteers, provides a free on-line resource for DirectX programmers.

Special thanks to WWW.MVPS.ORG, for providing a permanent home for this site.

Visitors Since 1/1/2000: Hit Counter
Last updated: 07/26/05.