Topic : DirectDraw Programming
Author : Lar Mader
Page : << Previous 4  
Go to page :


ddrval != DD_OK )
    {
        return(false);
    }

    // using DDSCL_NORMAL means we will coexist with GDI
    ddrval = lpDD->SetCooperativeLevel( hwnd, DDSCL_NORMAL );
    if( ddrval != DD_OK )
    {
        lpDD->Release();
        return(false);
    }

    memset( &ddsd, 0, sizeof(ddsd) );
    ddsd.dwSize = sizeof( ddsd );
    ddsd.dwFlags = DDSD_CAPS;
    ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;

    // The primary surface is not a page flipping surface this time
    ddrval = lpDD->CreateSurface( &ddsd, &lpDDSPrimary, NULL );
    if( ddrval != DD_OK )
    {
        lpDD->Release();
        return(false);
    }

    // Create a clipper to ensure that our drawing stays inside our window
    ddrval = lpDD->CreateClipper( 0, &lpClipper, NULL );
    if( ddrval != DD_OK )
    {
        lpDDSPrimary->Release();
        lpDD->Release();
        return(false);
    }

    // setting it to our hwnd gives the clipper the coordinates from our window
    ddrval = lpClipper->SetHWnd( 0, hwnd );
    if( ddrval != DD_OK )
    {
        lpClipper-> Release();
        lpDDSPrimary->Release();
        lpDD->Release();
        return(false);
    }

    // attach the clipper to the primary surface
    ddrval = lpDDSPrimary->SetClipper( lpClipper );
    if( ddrval != DD_OK )
    {
        lpClipper-> Release();
        lpDDSPrimary->Release();
        lpDD->Release();
        return(false);
    }

    memset( &ddsd, 0, sizeof(ddsd) );
    ddsd.dwSize = sizeof( ddsd );
    ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH;
    ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
    ddsd.dwWidth = 640;
    ddsd.dwHeight = 480;

    // create the backbuffer separately
    ddrval = lpDD->CreateSurface( &ddsd, &lpDDSBack, NULL );
    if( ddrval != DD_OK )
    {
        lpClipper-> Release();
        lpDDSPrimary->Release();
        lpDD->Release();
        return(false);
    }
    return(true);
}

The first difference between this code and the previous code for initializing DirectDraw is that you call SetCooperativeLevel() with the flag DDSCL_NORMAL. This means that we are not taking complete control of the display and therefore not removing GDI from the picture. When we create the Primary surface, we don't create a complex surface, because we are not preparing for page flipping.

Next we create a DirectDraw clipper for our window and attach it to the primary surface. The DirectDraw clipper is an object used to maintain a list of rectangles for clipping. By setting the clipper to our window handle, we are implicitly telling the clipper to clip using the client area of the window as the rectangle to clip. We then set this to our primary surface, so that blits to this surface are clipped per the client area of our window.

We create the backbuffer separately, and this time we must specify the size.

Now we are set to go. The last thing we need to do differently is handle the code where we would normally call Flip(), and instead do a blit. You can brew your own Flip that does the right thing like this:

bool MyFlip()
{
    HRESULT ddrval;
    RECT rcRectSrc;
    RECT rcRectDest;
    POINT p;

    // if we're windowed do the blit, else just Flip
    if (IsWindowed)
    {
        // first we need to figure out where on the primary surface our window lives
        p.x = 0; p.y = 0;
        ClientToScreen(ddWnd, &p);
        GetClientRect(ddWnd, &rcRectDest);
        OffsetRect(&rcRectDest, p.x, p.y);
        SetRect(&rcRectSrc, 0, 0, 640, 480);
        ddrval = lpDDSPrimary->Blt( &rcRectDest, lpDDSBack, &rcRectSrc, DDBLT_WAIT, NULL);
    } else {
        ddrval = lpDDSPrimary->Flip( NULL, DDFLIP_WAIT);
    }

    return (ddrval == DD_OK);
}


3. Losing Surfaces

One very important area of robustness that the code samples above ignored is what happens if the user switches away from a fullscreen DirectDraw app to some other app running in the system. First of all, your code needs to handle the WM_ACTIVATEAPP message and set a flag when your app goes inactive. In the idle section of your PeekMessage loop, check this flag, and don't try to update your display when you're not active.

But the real issue is that once you switch to another app, you have left the DirectDraw environment and allowed GDI to become active. GDI is oblivious to DirectDraw, and will overwrite the areas of display memory in use by your DirectDraw surfaces. This means that your bitmaps get wiped out and will need to be reloaded. Fortunately, when you call a function that operates on a surface, such as Flip() or BltFast(), DirectDraw will return an error of DDERR_SURFACELOST to let you know if the surface was lost. So, your code has to check for this error and reload the bitmaps into the surfaces if this error occurs.

ddrval = lpDDSPrimary->Blt( &rcRectDest, lpDDSBack, &rcRectSrc, DDBLT_WAIT, NULL);
if( ddrval == DDERR_SURFACELOST )
{
    ddrval = restoreAll();
}

ddrval = lpDDSPrimary->Flip( NULL, DDFLIP_WAIT);

if( ddrval == DDERR_SURFACELOST )
{
    ddrval = restoreAll();
}
:
:

void restoreAll()
{
    // for each surface your app has created you should do the following:
    ddrval = lpMyDDSurface->Restore(); // this reattaches the video memory to the surface
    if( ddrval == DD_OK )
    {
        lpTempDDS ->DDReLoadBitmap(); // this will be the same as the function above
                                      // that was originally used to load the bitmap
                                      // with the exception that the surface is already
                                      // created and ready to go
    }
}


4. Further Reading

The best reading I can recommend is the sample code that comes with the DirectX SDK. These samples are worth looking at as they cover some areas of robustness that this paper ignored. I have yet to find a book on DirectX that exposes anything advanced in the API. MSJ has published a couple of very good introductory DirectDraw and DirectSound articles, but nothing very advanced.


Page : << Previous 4