JavaScript EditorFree JavaScript Editor     Ajax Editor 



Main Page
  Previous Section Next Section

Page Flipping

Once you've created a complex surface with a primary surface and a back buffer surface, you're ready to page flip. The standard animation loop requires these steps (see Figure 7.12):

  1. Clear back buffer.

  2. Render scene to back buffer.

  3. Flip primary surface with back buffer surface.

  4. Lock to frame rate (30 fps, for example).

  5. Repeat step 1.

Figure 7.12. A page flipped animation system.

graphics/07fig12.gif

There are a few details that may confuse you. First, if the back buffer is flipped with the primary buffer, won't the back buffer become the primary buffer, and vice versa? If so, won't you need to draw on the primary surface every other frame? Although this may seem to make sense, it's not what really happens. In reality, pointers to VRAM are switched by the hardware, and from your point of view and DirectDraw's, the back buffer surface is always offscreen and the primary is always onscreen. Therefore, you always draw to the back buffer and flip with the primary surface each frame.

To flip the primary surface with the next attached surface in the flipping chain, you use the function IDIRECTDRAWSURFACE7::Flip(), shown here:

HRESULT Flip(LPDIRECTDRAWSURFACE7 lpDDSurfaceTargetOverride,  // override surface
             DWORD dwFlags); // control flags

This returns DD_OK if successful and an error code if not.

The parameters are simple. lpDDSurfaceTargetOverride is basically an advanced parameter used to override the flipping chain and flip to another surface other than the back buffer attached to the primary surface; just send NULL here. The dwFlags parameter, however, might be of interest to you. Table 7.2 contains the various settings for it.

Table 7.2. Control Flags for Flip()
Value Description
DDFLIP_INTERVAL2 Flip after two vertical retraces.
DDFLIP_INTERVAL3 Flip after three vertical retraces.
DDFLIP_INTERVAL4 Flip after four vertical retraces.

(Note that the default is one vertical retrace.)

These flags indicate how many vertical retraces to wait between each flip. The default is one. DirectDraw will return DERR_WASSTILLDRAWING for each surface involved in the flip until the specified number of vertical retraces has occurred. If DDFLIP_INTERVAL2 is set, DirectDraw will flip on every second vertical sync; if DDFLIP_INTERVAL3, on every third sync; and if DDFLIP_INTERVAL4, on every fourth sync.

These flags are effective only if DDCAPS2_FLIPINTERVAL is set in the DDCAPS structure returned for the device.

DDFLIP_NOVSYNC— This flag causes DirectDraw to perform the physical flip as close as possible to the next scan line.

DDFLIP_WAIT— This flag forces the hardware to wait until a flip is possible rather than returning back immediately if there's a problem.

TRICK

It's possible to create a complex surface with two back buffers or a flipping chain that has a total of three surfaces, including the primary surface. This is called triple buffering, and it gives the ultimate in performance. The reason is obvious: If you have a single back buffer, the video hardware may be bottlenecked by your accessing it along with the video hardware and so on. But with two extra surfaces in the flipping chain, the hardware never has to wait. The beauty of triple buffering with DirectDraw is that you simply use Flip() and the hardware flips the surfaces in a cyclic manner, but you still only render to a single back buffer, so it's transparent to you.


Typically, you'll set the flags for DDFLIP_WAIT and that's it. Also, you must call Flip() as a method from the primary surface, not the back buffer. This should make sense because the primary surface is the "parent" of the back buffer surface, and the back buffer is part of the parent's flipping chain. Anyway, here's how you would make the call to flip pages:

lpddsprimary->Flip(NULL, DDFLIP_WAIT);

And I've found that adding a little logic like this helps if the function errors out for some stupid reason:

while (FAILED(lpddsprimary->Flip(NULL, DDFLIP_WAIT)));

WARNING

Both the back buffer surface and the primary surface must be unlocked to perform the flip, so make sure you've unlocked them both before trying a call to Flip().


For an example of page flipping, check out DEMO7_5.CPP|EXE. I took DEMO7_4.CPP and changed the double buffering to page flipping, and of course I updated the Game_Init() code to create a complex surface with a single back buffer. Here are Game_Init() and Game_Main() for your review:

int Game_Init(void *parms = NULL, int num_parms = 0)
{
// this is called once after the initial window is created and
// before the main event loop is entered, do all your initialization
// here

LPDIRECTDRAW7 lpdd;

// first create base IDirectDraw interface
if (FAILED(DirectDrawCreate(NULL, (void **)&lpdd, IID_IDirectDraw7, NULL)))
   return(0);

// set cooperation to full screen
if (FAILED(lpdd->SetCooperativeLevel(main_window_handle,
               DDSCL_FULLSCREEN | DDSCL_ALLOWMODEX |
               DDSCL_EXCLUSIVE | DDSCL_ALLOWREBOOT)))
   return(0);

// set display mode to 640x480x8
if (FAILED(lpdd->SetDisplayMode(SCREEN_WIDTH, SCREEN_HEIGHT,
                                 SCREEN_BPP,0,0)))
   return(0);

// clear ddsd and set size
DDRAW_INIT_STRUCT(ddsd);

// enable valid fields
ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;

// set the backbuffer count field to 1, use 2 for triple 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)));

// build up the palette data array
for (int color=1; color < 255; color++)
    {
    // fill with random RGB values
    palette[color].peRed   = rand()%256;
    palette[color].peGreen = rand()%256;
    palette[color].peBlue  = rand()%256;

    // set flags field to PC_NOCOLLAPSE
    palette[color].peFlags = PC_NOCOLLAPSE;
    } // end for color

// now fill in entry 0 and 255 with black and white
palette[0].peRed     = 0;
palette[0].peGreen   = 0;
palette[0].peBlue    = 0;
palette[0].peFlags   = PC_NOCOLLAPSE;

palette[255].peRed   = 255;
palette[255].peGreen = 255;
palette[255].peBlue  = 255;
palette[255].peFlags = PC_NOCOLLAPSE;

// create the palette object
if (FAILED(lpdd->CreatePalette(DDPCAPS_8BIT | DDPCAPS_ALLOW256 |
                                DDPCAPS_INITIALIZE,
                                palette,&lpddpal, NULL)))
return(0);

// finally attach the palette to the primary surface
if (FAILED(lpddsprimary->SetPalette(lpddpal)))
   return(0);

// return success or failure or your own return code here
return(1);

} // end Game_Init

/////////////////////////////////////////////////////////
////

int Game_Main(void *parms = NULL, int num_parms = 0)
{
// this is the main loop of the game, do all your processing
// here

// make sure this isn't executed again
if (window_closed)
   return(0);

// for now test if user is hitting ESC and send WM_CLOSE
if (KEYDOWN(VK_ESCAPE))
   {
   PostMessage(main_window_handle,WM_CLOSE,0,0);
   window_closed = 1;
   } // end if

// lock the back buffer
DDRAW_INIT_STRUCT(ddsd);
lpddsback->Lock(NULL,&ddsd, DDLOCK_SURFACEMEMORYPTR | DDLOCK_WAIT,NULL);

// alias pointer to back buffer surface
UCHAR *back_buffer = (UCHAR *)ddsd.lpSurface;

// now clear the back buffer out

// linear memory?
if (ddsd.lPitch == SCREEN_WIDTH)
    memset(back_buffer,0,SCREEN_WIDTH*SCREEN_HEIGHT);
else
   {
   // non-linear memory

   // make copy of video pointer
   UCHAR *dest_ptr = back_buffer;

   // clear out memory one line at a time
   for (int y=0; y<SCREEN_HEIGHT; y++)
       {
       // clear next line
       memset(dest_ptr,0,SCREEN_WIDTH);

       // advance pointer to next line
       dest_ptr+=ddsd.lPitch;

       } // end for y

   } // end else

// you would perform game logic...

// 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
while (FAILED(lpddsprimary->Flip(NULL, DDFLIP_WAIT)));

// wait a sec
Sleep(500);

// return success or failure or your own return code here
return(1);

} // end Game_Main

Also, note the boldfaced code from Game_Main() that deals with the lock window_closed, reprinted here:

// make sure this isn't executed again
if (window_closed)
   return(0);

// for now test if user is hitting ESC and send WM_CLOSE
if (KEYDOWN(VK_ESCAPE))
   {
   PostMessage(main_window_handle,WM_CLOSE,0,0);
   window_closed = 1;
   } // end if

TRICK

I needed to add the exit state in the preceding code because it's possible that Game_Main() will be called one extra time even though the window was destroyed. This will cause an error, of course, because DirectDraw anchors to the window handle. Hence, I have created a locking variable (or binary semaphore if you will) that's set once the window is closed, and the gate keeps the Game_Main() function from any future entry. This is a very important detail that I should have mentioned in the last program, but I didn't. Of course, I could have rewritten the text, but I just wanted to show you how easy it is to make a mistake with DirectX/Win32 asynchronous programming.


That's about all there is to page flipping. DirectDraw does most of the work, but I want to leave you with some last details about it. First, when you create a back buffer, there is the possibility that DirectDraw will create it in system memory rather than VRAM (if there isn't any left). In that case, you don't have to do anything; DirectDraw will emulate the functionality of page flipping with double buffering and copy the back buffer to the primary surface when you make a call to Flip(). However, it will be slower. The cool thing is that your code will work no matter what. So that's pretty killer and drama-free, baby!

NOTE

In general, when you create the primary and secondary back buffer, you want them both in VRAM. The primary is always in VRAM, but it's possible to get stuck with a system memory back buffer. However, always remember that there's only so much VRAM, and you might want to forgo the use of a VRAM back buffer in exchange for putting all your game graphics in VRAM to speed the blitting of images. Using the hardware blitter to move bitmaps from VRAM to VRAM is much faster than moving them from system memory to VRAM. Alas, you might decide to make a system memory back buffer in cases where you have a lot of small sprites or bitmaps and you're going to do a lot of blitting. In this case, you're doing so much blitting, the speed loss of a double buffer scheme in deference to page flipping with a VRAM animation system is far outweighed by the performance gain of having all your game bitmaps in VRAM.


      Previous Section Next Section
    



    JavaScript EditorAjax Editor     JavaScript Editor