The article shows you a DirectDraw sample using Microsoft DirectX under Visual C++. DirectDraw is a component of Microsoft DirectX used for creating 2D graphics. The tutorial provides a step-by-step guide for setting up a DirectDraw application, creating surfaces, loading and manipulating images, and rendering graphics. By the going through this sample source code, you will have a functional DirectDraw application and a solid understanding of the core concepts involved in DirectX programming.
You can download the simple DirectDraw source code by using the following link.
DirectDraw Sample Program (18.1 KiB, 11,208 hits)
Table of Contents
- Setting up DirectX under Visual C++
- The DirectDraw sample
- DirectDraw error checking
- Initializing the DirectDraw system
- Setting the screen mode
- Creating surfaces
- Creating the Clipper
- Putting it all together
- Restoring lost surfaces
- The rendering loop
- The HeartBeat function
- Flipping surfaces
- Cleaning up
- Sample TODO
Setting up DirectX under Visual C++
Firstly, the directories must be set up so that Visual C/C++ can find the DirectX include files and libraries:
- Access the Tools/Options/Directories tabbed dialog.
- Select “library directories” from the drop-down list, and add the directory of the DX SDK libraries, e.g. “d:dxsdksdklib”
- Select “include directories” from the drop-down list, and add the directory of the DX SDK header files, e.g. “d:dxsdksdkinc”.
- If you are going to be using some of the DX utility headers used in the samples, then also add the samplesmisc directory, e.g. “d:dxsdksdksamplesmisc” to your includes path.
Note that the version of DirectX that normally ships with Visual C++ Compiler 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
The DirectDraw sample
The general outline of our sample DirectDraw application is as follows:
- Create a normal Windows window
- Set up our DirectX variables
- Initialize a DirectDraw object
- Set the “cooperative level” and display modes as necessary (explained later)
- Create front and back surfaces
- If in windowed mode, create and attach a clipper
- Render to the back buffer
- 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.
- Repeat from step 7 until we exit
- Clean up
Setting up
We are going to need a number of variables for our DirectDraw application. These can be global variables or class members, that’s up to you. The same goes for functions. Here are the variables we’re going to use:
1 2 3 4 5 6 | 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
The dd.h header file uses #ifndef and #define directives to include ddraw.h header file and other declarations.
1 2 3 4 5 6 7 8 9 10 11 12 | #ifndef _DD_H_ #define _DD_H_ #include <ddraw.h> extern LPDIRECTDRAW g_pDD; ... extern void DirectXFunction(); ... #endif |
dd.cpp
1 2 3 4 5 6 7 8 9 10 11 12 | #include "stdafx.h" #include "dd.h" #include <ddraw.h> LPDIRECTDRAW g_pDD=NULL; ... void DirectXFunction() { g_pDD = NULL; ... } |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 | 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.
1 2 3 4 5 6 7 8 9 10 11 | 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.
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
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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.
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | 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; } ... |
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”.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | ... 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; } ... |
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | ... //-- 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; } ... |
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
function:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | 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; } |
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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(); } } |
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.
1 2 3 4 5 6 7 8 9 10 | 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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.
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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 already explained.
Flipping surfaces
Now let’s look at the function that performs the surface flipping.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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.
Cleaning up
When we’re done with DirectX objects, we have to “release” them, which is done by calling Release on them, for example:
1 2 3 4 5 6 7 8 | void DDDone() { if (g_pDD != NULL) { g_pDD->Release(); g_pDD = NULL; } } |
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.