Okay, it's supposed to be really simple to run your game in both exclusive mode and windowed mode, but it does take some work to get it running properly (and elegantly). For this article, I'll be using C++ because it's very common in the industry. You can wrap this example in classes to make it easier, if that's what you want.
I'm also assuming that you're familiar with setting up and using DirectDraw in exclusive mode, so I won't go into the details of that here. Read on!
[size="5"]The Design
Many parts of DirectDraw initialization are different for windowed mode. The best way to do it is first to create the DirectDraw object when your program starts up. Second, you create all your surfaces, set the cooperative level, set the display mode, fill out any variables you need, etc. This second stage is where all the changes between exclusive mode and windowed mode are found. So, your functions are set up like this:
void CreateDirectDraw();
void DestroyDirectDraw();
void CreateSurfaces(bool bExclusive, int nWidth, int nHeight, int nBPP);
void DestroySurfaces();
We'll need to modify the game loop a little to handle windowed mode. We'll also need a function that handles switching modes that'll get it up and running:
void SwitchMode(bool bExclusive, int nWidth, int nHeight, int nBPP);
[size="5"]CreateSurfaces(...)
We'll split this function up into two sections, which initialize DirectDraw for either exclusive mode or windowed mode. That is done like this:
if( bExclusive )
{
// exclusive code
// save the mode
g_bExclusive = bExclusive;
}
else
{
// windowed code
// save the mode
g_bExclusive = bExclusive;
}
You can place all of the code you've already written in the exclusive code section. Have it use the nWidth, nHeight, and nBPP for the display mode, etc. We'll add our own code to the windowed code section. (I'll do this with several functions, splitting them up into two sections. Just so you know what I'm doing!)
As I mentioned before, when this function is called, DirectDraw has already been created. So, the next step in initializing DirectDraw is to set the cooperative level via lpDD->SetCooperativeLevel(). Pass the handle of your main window, and DDSCL_NORMAL:
lpDD->SetCooperativeLevel(hMainWnd, DDSCL_NORMAL);
You need a very different "buffering system" in windowed mode. You can't create a primary surface with attached back buffers and flip() them, because you don't have exclusive access to the video hardware. Flipping is the process of swapping the address of the current primary surface with one of its attached back buffers. Obviously, you can't do this in windowed mode, because you're sharing the primary surface with all the other apps.
The system you need in windowed mode is to create your primary surface with this DDSURFACEDESC2:
DDSURFACEDESC2 ddsd;
ZeroMemory(&ddsd, sizeof(ddsd));
ddsd.dwSize = sizeof( ddsd );
ddsd.dwFlags = DDSD_CAPS;
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;
Then you create your back buffer like so:
DDSURFACEDESC2 ddsd;
ZeroMemory(&ddsd, sizeof(ddsd));
ddsd.dwSize = sizeof( ddsd );
ddsd.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT;
ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
ddsd.dwWidth = 640; // whatever you want
ddsd.dwHeight = 480; // whatever you want
Remember, in DirectDraw, the primary surface is always the entire screen. To keep yourself from drawing all over the screen in windowed mode, you attach a clipper to your primary surface, and attach it to your main window as well (that's the easy way):
LPDIRECTDRAWCLIPPER lpddClipper;
lpDD->CreateClipper(...lpddClipper...);
lpddClipper->SetHWnd(...hMainWnd...);
lpddsPrimary->SetClipper(...lpddClipper...);
Well, that's it for the CreateSurfaces function. We'll look at cleaning up next.
[size="5"]DestroySurfaces(...)
Your shutdown code is also different. Instead of releasing just the DirectDraw object and the primary surface, you also have to release your back buffer and clipper. Again, use an if statement to separate the exclusive code from the windowed code:
if( bExclusive )
{
// exclusive code
}
else
{
// windowed code
}
This is the code to add in the windowed code section:
if( lpddBack )
{
// release the back buffer
lpddBack->Release();
lpddBack = NULL;
}
if( lpddPrimary )
{
// release the clipper (indirectly)
lpddPrimary->SetClipper(NULL);
lpddClipper = NULL;
// release the primary surface
lpddPrimary->Release();
lpddPrimary = NULL;
}
[size="5"]The Game Loop
You may think that the only real difference in the game loop is that you blit the back buffer to the primary surface when you're in windowed mode instead of flipping... We'll start with that part though. Split your rendering function into two sections, separated by an if statement:
if( g_bExclusive )
{
// exclusive code
}
else
{
// windowed code
}
Put your old exclusive mode rendering code in the exclusive code section, then add a call to Blt in the windowed code section. Use the primary surface as the destination, and the back buffer as the source, like this:
lpddPrimary->Blt(NULL, lpddBack, NULL, DDBLT_WAIT, NULL);
// calculate the client rect in screen coordinates
RECT rect;
ZeroMemory(▭, sizeof( rect ));
// get the client area
GetClientRect(hMainWnd, ▭);
// copy the rect's data into two points
POINT p1;
POINT p2;
p1.x = rect.left;
p1.y = rect.top;
p2.x = rect.right;
p2.y = rect.bottom;
// convert it to screen coordinates (like DirectDraw uses)
ClientToScreen(hMainWnd, &p1);
ClientToScreen(hMainWnd, &p2);
// copy the two points' data back into the rect
rect.left = p1.x;
rect.top = p1.y;
rect.right = p2.x;
rect.bottom = p2.y;
// blit the back buffer to our window's position
g_lpPrimary->Blt(▭, g_lpBack, NULL, DDBLT_WAIT, NULL);
So, how do we do that? Well, what consumes most of the computer's power? The game loop, of course! So, we "pause" the game automatically when the user switches to something else, and "resume" it automatically when the user switches back to our app. (In games like massive online multiplayer games, this is not plausible; you'll have to think of something else. Tip: you could at least pause the rendering, or slow it down a little.) Anyway, to pause the game loop, we add a variable to keep track of whether the game is running or not:
bool bRunGame;
if( LOWORD( wParam ) == WA_INACTIVE )
{
// the user is now working with another app
bRunGame = false;
}
else
{
// the user has now switched back to our app
bRunGame = true;
}
MSG msg;
ZeroMemory(&msg, sizeof(msg));
for( ;; )
{
if( PeekMessage(&msg, NULL, NULL, NULL, PM_NOREMOVE) )
{
// retrieve a message
GetMessage(&msg, NULL, NULL, NULL);
if( msg.message == WM_QUIT )
break; // only way out of the for( ;; ) loop
// dispatch the message to our WndProc
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else
{
if( bRunGame )
{
// game code here
}
}
}
return msg.wParam;
[size="5"]Switching Modes While Running
Ah, we finally get to switching modes...
Well, we'll use a single function to switch modes. It'll be capable of changing between exclusive and windowed, and setting the display mode (while in exclusive mode). Here it is:
void ChangeDisplayMode(bool bExclusive, int nWidth, int nHeight, int nBPP);
ChangeDisplayMode(false, 0, 0, 0); // windowed
ChangeDisplayMode(true, 640, 480, 16); // 640x480x16 exclusive
ChangeDisplayMode(true, 800, 600, 32); // 800x600x32 exclusive
ChangeDisplayMode(bool bExclusive, int nWidth, int nHeight, int nBPP)
{
// destroy any existing surfaces and clippers.
DestroySurfaces();
// create new surfaces and change the
// cooperative level and display mode
CreateSurfaces(bExclusive, int nWidth, int nHeight, int nBPP);
}
There are lots of ways to wring more performance out of a windowed mode DirectX app. There are also a few ways to make it easier for the end user to use. We'll try to achieve a blend of both. You'll have to wait for the next article for those tips though!
Good Luck!
- null_pointer
e-mail: [email="ratt96963@aol.com"]ratt96963@aol.com[/email]
web site: http://www.freeyello...rs8/nullpointer
site name: Sabre Multimedia