scorpioncity.com
scorpioncity.com · This is/was my old 'personal website' (~1997 to 2010-ish); my new 'personal website' is at djoffe.com »
As of Oct 2016 I have decided to add some of the old content back again. Note that some of the info on the site is now outdated.
- David Joffe
Game programming with DirectX
[ Home | Chapter 1 | Chapter 2 | Chapter 3 | Chapter 4 | Chapter 5 | Chapter 6 ]

Chapter 3: A simple DirectDraw sample

This is a very simple DirectDraw sample. The source code for this sample is included in ddsamp.zip.

1. Setting up DirectX under Visual C/C++

I most likely won't be doing DirectX development under Watcom or Borland C/C++ or Delphi or VisualBasic etc; so if you want such info included here, you'll have to send it to me.

Firstly, the directories must be set up so that Visual C/C++ can find the DirectX include files and libraries:

  1. Access the Tools/Options/Directories tabbed dialog.
  2. Select "library directories" from the drop-down list, and add the directory of the DX SDK libraries, e.g. "d:\dxsdk\sdk\lib"
  3. Select "include directories" from the drop-down list, and add the directory of the DX SDK header files, e.g. "d:\dxsdk\sdk\inc".
  4. If you are going to be using some of the DX utility headers used in the samples, then also add the samples\misc directory, e.g. "d:\dxsdk\sdk\samples\misc" to your includes path.

Note that the version of DirectX that normally ships with Visual C++ isn't usually the latest, so to make sure that the compiler doesn't find the older version located in its own directories, add the include and library paths for the SDK in front of the default include and library paths.

You must also for each application that uses DirectX explicitly add the required libraries to the project. Do this in Project/Settings (Alt+F7), under the "Link" tab for each configuration of your project. For DirectDraw, add ddraw.lib in the Object/Library modules box. You also need to add dxguid.lib here if your application uses any of the DirectX COM interface ID's, eg IID_IDirectDraw7

2. The DirectDraw sample

Here is a screenshot of the application:

[Screenshot 1]

The general outline of our sample DirectDraw application is as follows:

  1. Create a normal Windows window
  2. Set up our DirectX variables
  3. Initialize a DirectDraw object
  4. Set the "cooperative level" and display modes as necessary (explained later)
  5. Create front and back surfaces
  6. If in windowed mode, create and attach a clipper
  7. Render to the back buffer
  8. Perform the flipping. If in full-screen mode, just flip. If in windowed mode, you need to blit from the back surface to the primary surface each frame.
  9. Repeat from step 7 until we exit
  10. Clean up

3. Setting up

We are going to need a number of variables for our DirectDraw application. These can be global variables or class members, thats up to you. The same goes for functions. Here are the variables we're going to use:

LPDIRECTDRAW        g_pDD;         // DirectDraw object
LPDIRECTDRAWSURFACE g_pDDSPrimary; // DirectDraw primary surface
LPDIRECTDRAWSURFACE g_pDDSBack;    // DirectDraw back surface
LPDIRECTDRAWCLIPPER g_pClipper;    // Clipper for windowed mode
HWND                g_hWnd;        // Handle of window
bool                g_bFullScreen; // are we in fullscreen mode?

All of these variables and functions I place in a seperate file, which can be called anything you want, although you should not use file names that already exist, such as "ddraw.h". This compiler is likely to get confused about which one you want. I've used dd.h and dd.cpp in the sample.

Remember to ensure that these variables are initialized to NULL before we begin. If you were creating classes, you could do this in the constructor of the class.

Here is the general layout of my dd.h and dd.cpp files:

dd.h

#ifndef _DD_H_
#define _DD_H_

#include <ddraw.h>

extern LPDIRECTDRAW g_pDD;
...

extern void DirectXFunction();
...

#endif

dd.cpp

#include "stdafx.h"
#include "dd.h"
#include <ddraw.h>

LPDIRECTDRAW g_pDD=NULL;
...

void DirectXFunction()
{
	g_pDD = NULL;
...
}

4. DirectDraw error checking

Before we begin, we should define a "clean" way of checking and debugging error codes from DirectX functions.

We create some functions to help us return and report error strings from HRESULT error codes.

A function that returns a string with the name of an HRESULT code:

char *DDErrorString(HRESULT hr)
{
	switch (hr)
	{
	case DDERR_ALREADYINITIALIZED:           return "DDERR_ALREADYINITIALIZED";
	case DDERR_CANNOTATTACHSURFACE:          return "DDERR_CANNOTATTACHSURFACE";
	case DDERR_CANNOTDETACHSURFACE:          return "DDERR_CANNOTDETACHSURFACE";
	case DDERR_CURRENTLYNOTAVAIL:            return "DDERR_CURRENTLYNOTAVAIL";
	case DDERR_EXCEPTION:                    return "DDERR_EXCEPTION";
	case DDERR_GENERIC:                      return "DDERR_GENERIC";
	case DDERR_HEIGHTALIGN:                  return "DDERR_HEIGHTALIGN";
	case DDERR_INCOMPATIBLEPRIMARY:          return "DDERR_INCOMPATIBLEPRIMARY";
	case DDERR_INVALIDCAPS:                  return "DDERR_INVALIDCAPS";
	case DDERR_INVALIDCLIPLIST:              return "DDERR_INVALIDCLIPLIST";
	case DDERR_INVALIDMODE:                  return "DDERR_INVALIDMODE";
	case DDERR_INVALIDOBJECT:                return "DDERR_INVALIDOBJECT";
	case DDERR_INVALIDPARAMS:                return "DDERR_INVALIDPARAMS";
	case DDERR_INVALIDPIXELFORMAT:           return "DDERR_INVALIDPIXELFORMAT";
	case DDERR_INVALIDRECT:                  return "DDERR_INVALIDRECT";
	case DDERR_LOCKEDSURFACES:               return "DDERR_LOCKEDSURFACES";
	case DDERR_NO3D:                         return "DDERR_NO3D";
	case DDERR_NOALPHAHW:                    return "DDERR_NOALPHAHW";
	case DDERR_NOCLIPLIST:                   return "DDERR_NOCLIPLIST";
	case DDERR_NOCOLORCONVHW:                return "DDERR_NOCOLORCONVHW";
	case DDERR_NOCOOPERATIVELEVELSET:        return "DDERR_NOCOOPERATIVELEVELSET";
	case DDERR_NOCOLORKEY:                   return "DDERR_NOCOLORKEY";
	case DDERR_NOCOLORKEYHW:                 return "DDERR_NOCOLORKEYHW";
	case DDERR_NODIRECTDRAWSUPPORT:          return "DDERR_NODIRECTDRAWSUPPORT";
	case DDERR_NOEXCLUSIVEMODE:              return "DDERR_NOEXCLUSIVEMODE";
	case DDERR_NOFLIPHW:                     return "DDERR_NOFLIPHW";
	case DDERR_NOGDI:                        return "DDERR_NOGDI";
	case DDERR_NOMIRRORHW:                   return "DDERR_NOMIRRORHW";
	case DDERR_NOTFOUND:                     return "DDERR_NOTFOUND";
	case DDERR_NOOVERLAYHW:                  return "DDERR_NOOVERLAYHW";
	case DDERR_NORASTEROPHW:                 return "DDERR_NORASTEROPHW";
	case DDERR_NOROTATIONHW:                 return "DDERR_NOROTATIONHW";
	case DDERR_NOSTRETCHHW:                  return "DDERR_NOSTRETCHHW";
	case DDERR_NOT4BITCOLOR:                 return "DDERR_NOT4BITCOLOR";
	case DDERR_NOT4BITCOLORINDEX:            return "DDERR_NOT4BITCOLORINDEX";
	case DDERR_NOT8BITCOLOR:                 return "DDERR_NOT8BITCOLOR";
	case DDERR_NOTEXTUREHW:                  return "DDERR_NOTEXTUREHW";
	case DDERR_NOVSYNCHW:                    return "DDERR_NOVSYNCHW";
	case DDERR_NOZBUFFERHW:                  return "DDERR_NOZBUFFERHW";
	case DDERR_NOZOVERLAYHW:                 return "DDERR_NOZOVERLAYHW";
	case DDERR_OUTOFCAPS:                    return "DDERR_OUTOFCAPS";
	case DDERR_OUTOFMEMORY:                  return "DDERR_OUTOFMEMORY";
	case DDERR_OUTOFVIDEOMEMORY:             return "DDERR_OUTOFVIDEOMEMORY";
	case DDERR_OVERLAYCANTCLIP:              return "DDERR_OVERLAYCANTCLIP";
	case DDERR_OVERLAYCOLORKEYONLYONEACTIVE: return "DDERR_OVERLAYCOLORKEYONLYONEACTIVE";
	case DDERR_PALETTEBUSY:                  return "DDERR_PALETTEBUSY";
	case DDERR_COLORKEYNOTSET:               return "DDERR_COLORKEYNOTSET";
	case DDERR_SURFACEALREADYATTACHED:       return "DDERR_SURFACEALREADYATTACHED";
	case DDERR_SURFACEALREADYDEPENDENT:      return "DDERR_SURFACEALREADYDEPENDENT";
	case DDERR_SURFACEBUSY:                  return "DDERR_SURFACEBUSY";
	case DDERR_CANTLOCKSURFACE:              return "DDERR_CANTLOCKSURFACE";
	case DDERR_SURFACEISOBSCURED:            return "DDERR_SURFACEISOBSCURED";
	case DDERR_SURFACELOST:                  return "DDERR_SURFACELOST";
	case DDERR_SURFACENOTATTACHED:           return "DDERR_SURFACENOTATTACHED";
	case DDERR_TOOBIGHEIGHT:                 return "DDERR_TOOBIGHEIGHT";
	case DDERR_TOOBIGSIZE:                   return "DDERR_TOOBIGSIZE";
	case DDERR_TOOBIGWIDTH:                  return "DDERR_TOOBIGWIDTH";
	case DDERR_UNSUPPORTED:                  return "DDERR_UNSUPPORTED";
	case DDERR_UNSUPPORTEDFORMAT:            return "DDERR_UNSUPPORTEDFORMAT";
	case DDERR_UNSUPPORTEDMASK:              return "DDERR_UNSUPPORTEDMASK";
	case DDERR_VERTICALBLANKINPROGRESS:      return "DDERR_VERTICALBLANKINPROGRESS";
	case DDERR_WASSTILLDRAWING:              return "DDERR_WASSTILLDRAWING";
	case DDERR_XALIGN:                       return "DDERR_XALIGN";
	case DDERR_INVALIDDIRECTDRAWGUID:        return "DDERR_INVALIDDIRECTDRAWGUID";
	case DDERR_DIRECTDRAWALREADYCREATED:     return "DDERR_DIRECTDRAWALREADYCREATED";
	case DDERR_NODIRECTDRAWHW:               return "DDERR_NODIRECTDRAWHW";
	case DDERR_PRIMARYSURFACEALREADYEXISTS:  return "DDERR_PRIMARYSURFACEALREADYEXISTS";
	case DDERR_NOEMULATION:                  return "DDERR_NOEMULATION";
	case DDERR_REGIONTOOSMALL:               return "DDERR_REGIONTOOSMALL";
	case DDERR_CLIPPERISUSINGHWND:           return "DDERR_CLIPPERISUSINGHWND";
	case DDERR_NOCLIPPERATTACHED:            return "DDERR_NOCLIPPERATTACHED";
	case DDERR_NOHWND:                       return "DDERR_NOHWND";
	case DDERR_HWNDSUBCLASSED:               return "DDERR_HWNDSUBCLASSED";
	case DDERR_HWNDALREADYSET:               return "DDERR_HWNDALREADYSET";
	case DDERR_NOPALETTEATTACHED:            return "DDERR_NOPALETTEATTACHED";
	case DDERR_NOPALETTEHW:                  return "DDERR_NOPALETTEHW";
	case DDERR_BLTFASTCANTCLIP:              return "DDERR_BLTFASTCANTCLIP";
	case DDERR_NOBLTHW:                      return "DDERR_NOBLTHW";
	case DDERR_NODDROPSHW:                   return "DDERR_NODDROPSHW";
	case DDERR_OVERLAYNOTVISIBLE:            return "DDERR_OVERLAYNOTVISIBLE";
	case DDERR_NOOVERLAYDEST:                return "DDERR_NOOVERLAYDEST";
	case DDERR_INVALIDPOSITION:              return "DDERR_INVALIDPOSITION";
	case DDERR_NOTAOVERLAYSURFACE:           return "DDERR_NOTAOVERLAYSURFACE";
	case DDERR_EXCLUSIVEMODEALREADYSET:      return "DDERR_EXCLUSIVEMODEALREADYSET";
	case DDERR_NOTFLIPPABLE:                 return "DDERR_NOTFLIPPABLE";
	case DDERR_CANTDUPLICATE:                return "DDERR_CANTDUPLICATE";
	case DDERR_NOTLOCKED:                    return "DDERR_NOTLOCKED";
	case DDERR_CANTCREATEDC:                 return "DDERR_CANTCREATEDC";
	case DDERR_NODC:                         return "DDERR_NODC";
	case DDERR_WRONGMODE:                    return "DDERR_WRONGMODE";
	case DDERR_IMPLICITLYCREATED:            return "DDERR_IMPLICITLYCREATED";
	case DDERR_NOTPALETTIZED:                return "DDERR_NOTPALETTIZED";
	case DDERR_UNSUPPORTEDMODE:              return "DDERR_UNSUPPORTEDMODE";
	case DDERR_NOMIPMAPHW:                   return "DDERR_NOMIPMAPHW";
	case DDERR_INVALIDSURFACETYPE:           return "DDERR_INVALIDSURFACETYPE";
	case DDERR_DCALREADYCREATED:             return "DDERR_DCALREADYCREATED";
	case DDERR_CANTPAGELOCK:                 return "DDERR_CANTPAGELOCK";
	case DDERR_CANTPAGEUNLOCK:               return "DDERR_CANTPAGEUNLOCK";
	case DDERR_NOTPAGELOCKED:                return "DDERR_NOTPAGELOCKED";
	case DDERR_NOTINITIALIZED:               return "DDERR_NOTINITIALIZED";
	}
	return "Unknown Error";
}

A function that we can use in our code to help us check for errors. It checks if an HRESULT is a failure, and if it is, it prints a debugging message and returns true, otherwise it returns false.

bool DDFailedCheck(HRESULT hr, char *szMessage)
{
	if (FAILED(hr))
	{
		char buf[1024];
		sprintf( buf, "%s (%s)\n", szMessage, DDErrorString(hr) );
		OutputDebugString( buf );
		return true;
	}
	return false;
}

Some lazy coders think that they can get away without doing much error checking. With DirectX, this is a very bad idea. You will have errors.

5. Initializing the DirectDraw system

After having created a Windows window (using MFC or plain Win32), we initialize the DirectDraw system, by creating an "IDirectDraw" object.

The DirectDrawCreate or DirectDrawCreateEx function calls can be used to create a DirectDraw object. You only create a single DirectDraw object for your application

bool DDInit( HWND hWnd )
{
	HRESULT hr;

	g_hWnd = hWnd;

	// Initialize DirectDraw
	hr = DirectDrawCreate( NULL, &g_pDD, NULL );
	if (DDFailedCheck(hr, "DirectDrawCreate failed" ))
		return false;

	return true;
}

Note that DirectDrawCreate will create an "old" DirectDraw that does not support the functions that "new" DirectDraw interfaces (such as an IDirectDraw7) does. Use DirectDrawCreateEx to create a DirectDraw interface that does. For our simple sample the above is sufficient.

6. Setting the screen mode

The remaining DirectDraw initialization (setting modes, creating surfaces and clippers) I place in a single function called CreateSurfaces.

The function SetCooperativeLevel is used to tell the system whether or not we want to use full-screen mode or windowed mode. In full-screen mode, we have to get exclusive access to the DirectDraw device, and then set the display mode. For windowed mode, we set the cooperative level to normal.

bool DDCreateSurfaces( bool bFullScreen)
{
	HRESULT hr; // Holds return values for DirectX function calls

	g_bFullScreen = bFullScreen;

	// If we want to be in full-screen mode
	if (g_bFullScreen)
	{
		// Set the "cooperative level" so we can use full-screen mode
		hr = g_pDD->SetCooperativeLevel(g_hWnd, DDSCL_EXCLUSIVE|DDSCL_FULLSCREEN|DDSCL_NOWINDOWCHANGES);
		if (DDFailedCheck(hr, "SetCooperativeLevel"))
			return false;

		// Set 640x480x256 full-screen mode
		hr = g_pDD->SetDisplayMode(640, 480, 8);
		if (DDFailedCheck(hr, "SetDisplayMode" ))
			return false;
	}
	else
	{
		// Set DDSCL_NORMAL to use windowed mode
		hr = g_pDD->SetCooperativeLevel(g_hWnd, DDSCL_NORMAL);
		if (DDFailedCheck(hr, "SetCooperativeLevel windowed" ))
			return false;
	}

	...

7. Creating surfaces

OK ... now that we've got that bit of initialization out of the way, we need to create a flipping structure. No, I'm not cursing the structure .. "flipping" as in screen page-flipping :).

Anyway, we need to create one main surface that everyone will see, and a "back" surface. All drawing is done to the back surface. When we are finished drawing we need to make what we've drawn visible. In full-screen mode, we just need to call a routine called Flip, which will turn the current back surface into the primary surface and vice versa. In windowed mode, we don't actually flip the surfaces - we copy the contents of the back buffer onto the primary buffer, which is what's inside the window. In other words, we "blit" the back surface onto the primary surface.

Anyway, here is the bit of code to create the surfaces. Right now the code is ignoring full-screen mode and only catering for windowed mode, but that'll change. Also, if there are errors in this code, consider them "exercises" ... :).

	...

	DDSURFACEDESC ddsd; // A structure to describe the surfaces we want
	// Clear all members of the structure to 0
	memset(&ddsd, 0, sizeof(ddsd));
	// The first parameter of the structure must contain the size of the structure
	ddsd.dwSize = sizeof(ddsd);

	if (g_bFullScreen)
	{
		// Screw the full-screen mode (for now) (FIXME)
	}
	else
	{

		//-- Create the primary surface

		// The dwFlags paramater tell DirectDraw which DDSURFACEDESC
		// fields will contain valid values
		ddsd.dwFlags = DDSD_CAPS;
		ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;

		hr = g_pDD->CreateSurface(&ddsd, &g_pDDS, NULL);
		if (DDFailedCheck(hr, "Create primary surface"))
			return false;

		//-- Create the back buffer

		ddsd.dwFlags = DDSD_WIDTH | DDSD_HEIGHT | DDSD_CAPS;
		// Make our off-screen surface 320x240
		ddsd.dwWidth = 320;
		ddsd.dwHeight = 240;
		// Create an offscreen surface
		ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;

		hr = g_pDD->CreateSurface(&ddsd, &g_pDDSBack, NULL);
		if (DDFailedCheck(hr, "Create back surface"))
			return false;

	}

	...

8. Creating the Clipper

Now that we've created the surfaces, we need to create a clipper (if we're running in windowed mode), and attach the clipper to the primary surface. This prevents DirectDraw from drawing outside the windows client area.

	...

	//-- Create a clipper for the primary surface in windowed mode
	if (!g_bFullScreen)
	{

		// Create the clipper using the DirectDraw object
		hr = g_pDD->CreateClipper(0, &g_pClipper, NULL);
		if (DDFailedCheck(hr, "Create clipper"))
			return false;

		// Assign your window's HWND to the clipper
		hr = g_pClipper->SetHWnd(0, g_hWnd);
		if (DDFailedCheck(hr, "Assign hWnd to clipper"))
			return false;

		// Attach the clipper to the primary surface
		hr = g_pDDS->SetClipper(g_pClipper);
		if (DDFailedCheck(hr, "Set clipper"))
			return false;
	}

	...

9. Putting it all together

Now that we have all these initialization routines, we need to actually call them, so the question is, where to call them?

In an MFC application, a logical place to do this is in the application's InitInstance routine:

BOOL CYourAppNameHereApp::InitInstance()
{
	... All the other MFC initialization junk here ..

	// Initialize DirectDraw
	if (!DDInit( AfxGetMainWnd()->GetSafeHwnd() ))
	{
		AfxMessageBox( "Failed to initialize DirectDraw" );
		return FALSE;
	}

	// Create DirectDraw surfaces
	if (!DDCreateSurfaces( false ))
	{
		AfxMessageBox( "Failed to create surfaces" );
		return FALSE;
	}

	return TRUE;
}

In a plain Win32 application, you can do this in your WinMain function just before you enter the main message loop, but after you've created your window:

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{
	MSG  Msg;

	g_hInstance = hInstance;

	if (!hPrevInstance) {
		if (!Register( g_hInstance ))
			return FALSE;
	}

	// Create the main window
	g_hwndMain = Create( nCmdShow, 320, 240 );
	if (!g_hwndMain)
		return FALSE;

	// Initialize DirectDraw
	if (!DDInit( g_hwndMain ))
	{
		MessageBox( g_hwndMain, "Failed to initialize DirectDraw", "Error", MB_OK );
		return 0;
	}

	// Create DirectDraw surfaces
	if (!DDCreateSurfaces( false ))
	{
		MessageBox( g_hwndMain, "Failed to create surfaces", "Error", MB_OK );
		return 0;
	}

	while (GetMessage(&Msg, NULL, 0, 0))
	{
		TranslateMessage(&Msg);
		DispatchMessage(&Msg);
	}

	return Msg.wParam;
}

10. Restoring lost surfaces

As if all this initialization wasn't enough, we also have to make sure our DirectDraw surfaces are not getting "lost". The memory associated with DirectDraw surfaces can be released under certain circumstances, because it has to share resources with the Windows GDI. So each time we render, we first have to check if our surfaces have been lost and Restore them if they have. This is accomplished with the IsLost function.

void CheckSurfaces()
{
	// Check the primary surface
	if (g_pDDS)
	{
		if (g_pDDS->IsLost() == DDERR_SURFACELOST)
			g_pDDS->Restore();
	}
	// Check the back buffer
	if (g_pDDSBack)
	{
		if (g_pDDSBack->IsLost() == DDERR_SURFACELOST)
			g_pDDSBack->Restore();
	}
}

11. The rendering loop

Now that we've got most of the general initialization out of the way, we need to set up a rendering loop. This is basically the main loop of the game, the so-called HeartBeat function. So we're going to call it just that.

The HeartBeat function gets called during your applications idle-time processing, which is typically whenever the window has no more messages to process.

MFC: We can override the application's OnIdle function and call our HeartBeat function from there. Use ClassWizard or the toolbar wizard to create a handler for "idle-time processing" for your main application class.

BOOL CYourMFCAppNameHereApp::OnIdle(LONG lCount)
{
	CWinApp::OnIdle(lCount); // Call the parent default OnIdle handler

	// Our game's heartbeat function
	HeartBeat();

	// Request more idle-time, so that we can render the next loop!
	return TRUE;
}

Win32: We can call the heartbeat function from inside the message loop, by using the function PeekMessage in our WinMain function to determine if we have any messages waiting:

	g_bRunning = true;
	while (g_bRunning)
	{
		while (PeekMessage(&Msg, g_hwndMain, 0, 0, PM_NOREMOVE))
		{
			BOOL bGetResult = GetMessage(&Msg, NULL, 0, 0);
			TranslateMessage(&Msg);
			DispatchMessage(&Msg);
			if (bGetResult==0)
				g_bRunning = false;
		}
		if (g_bRunning)
		{
			CheckSurfaces();
			HeartBeat();
		}
	}

There are alternate ways to decide when to call the HeartBeat function, for example you could use a timer. The method you use depends on the type of game you are making. If you are making a first-person 3D shooter, you probably want as high a frame rate as possible, so you might use the idle-time method. If you are making a 2D scrolling game, this might not be optimal, as you may want to control the frame rate.

12. The HeartBeat function

Now let's look at the heartbeat function. The function checks for lost surfaces, then clears the back buffer with black, then draws a color square to the back buffer, and then flips the back buffer to the front.

void HeartBeat()
{
	// Check for lost surfaces
	CheckSurfaces();

	// Clear the back buffer
	DDClear( g_pDDSBack, 0, 0, 320, 240 );

	static int iFoo = 0;
	// Draw a weird looking color square
	for ( int r=0; r<64; r++ )
	{
		for ( int g=0; g<64; g++ )
		{
			DDPutPixel( g_pDDSBack, g, r, (r*2+iFoo)%256, (g+iFoo)%256, (63-g)*4 );
		}
	}
	iFoo++;

	// Blit the back buffer to the front buffer
	DDFlip();

The DDPutPixel function used here is explained in Chapter 5.

13. Flipping surfaces

Now let's look at the function that performs the surface flipping.

void DDFlip()
{
	HRESULT hr;

	// if we're windowed do the blit, else just Flip
	if (!g_bFullScreen)
	{
		RECT    rcSrc;  // source blit rectangle
		RECT    rcDest; // destination blit rectangle
		POINT   p;

		// find out where on the primary surface our window lives
		p.x = 0; p.y = 0;
		::ClientToScreen(g_hWnd, &p);
		::GetClientRect(g_hWnd, &rcDest);
		OffsetRect(&rcDest, p.x, p.y);
		SetRect(&rcSrc, 0, 0, 320, 240);
		hr = g_pDDS->Blt(&rcDest, g_pDDSBack, &rcSrc, DDBLT_WAIT, NULL);
	}
	else
	{
		hr = g_pDDS->Flip(NULL, DDFLIP_WAIT);
	}
}

A primary surface in windowed mode represents the entire Windows screen, so we have to first find out where on the screen our window is, and then translate by that offset in order to blit into the Window.

Note the Blt parameter DDBLT_WAIT. By default, if a surface is "busy" when you call Blt (for example if the GDI is accessing it) then DirectDraw will return an error, without performing the blit. Passing the DDBLT_WAIT option will instruct DirectDraw to wait until the surface becomes available and then perform the blit.

14. Cleaning up

When we're done with DirectX objects, we have to "release" them, which is done by calling Release on them, for example:

void DDDone()
{
	if (g_pDD != NULL)
	{
		g_pDD->Release();
		g_pDD = NULL;
	}
}

15. Sample TODO

There are a few things the sample can't do yet. For one thing, full-screen mode doesn't work properly yet. It should also demonstrate how to handle switching between windowed and full-screen modes.


[ Home | Chapter 1 | Chapter 2 | Chapter 3 | Chapter 4 | Chapter 5 | Chapter 6 ]
Follow @d_joffe