Having trouble getting DDraw running in windowed mode
Obviously, I am a bit of a newbie when it comes to DirectX and C++. There is a wealth of info on the internet (not the least of which is this site!) on how to get DirectDraw up and running in full screen, exclusive mode with a backbuffer. However, I''d like to make my core set of DDraw functions as flexible as possible, and to do that I need to be able to run my game in Windowed mode as well as Full Screen. How would I go about switching to/from windowed mode? I know how to trap Alt-Enter, but not how to tell DDraw to jump into a normal window without crashing. Any ideas? I have posted the source code if that would help. It''s probably something quite obvious, like creating the backbuffer in the wrong way or not attaching the clipper correctly, but I''d appreciate anything you can tell me. Thank you!
Anthracks
Source
Ok, it's supposed to be really simple, but it does take some work to get it running properly.
You need a very different "buffering system" in windowed mode. You can't create attached back buffers and flip() them, because you don't have exclusive access to the video hardware. (what if other windowed mode apps tried to flip at the same time?) Also, you don't own the back buffer memory --> every app would have to share it...
The system you need in windowed 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.dwFlags = 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
Note that you did NOT use the DDSCAPS_BACKBUFFER flag in there -- that's only for fullscreen mode apps. Instead of flipping, you call Blt() from the primary surface, specifying the back buffer as the source surface:
lpddsPrimary->Blt(...lpddsBack...);
Finally, 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...);
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.
I'd recommend making an InitDirectDraw(bool) function that handles both (hence the bool flag), and a FreeDirectDraw(bool) that handles both too. You might as well destroy everything and create it from scratch when you switch between windowed and fullscreen, because the differences are largely in these two functions. Also, you can't set the display mode in windowed mode, but you can get the display mode. Use GetSystemMetrics(SM_CXSCREEN) (and SM_CYSCREEN) to determine the width and height. There's also a parameter that you can use in that function to get the bit depth --> you'll need that for loading bitmaps and modifying surfaces.
Finally, switching between modes isn't to hard. DON'T track something like ALT+TAB. It's bad coding...what if Windows came up with another method of switching apps? You code would crash every time. Instead, handle the WM_ACTIVATE message in your WndProc. It's simple: one of the parameters is a flag that tells you whether or not the window is being activated, or not. Simply call FreeDirectDraw(...) and InitDirectDraw(...) with the right flag and you're set. Add the lpDD->RestoreAllSurfaces() call to WndProc, or maybe to your drawing functions.
I know I said finally in the last paragraph, but there's one more point: you can't hog the system anymore. You have to give it back to Windows when the user goes to another window. How? Use the other parameter of the WM_ACTIVATE message (simple, huh?) It tells you when your app is either getting or losing the focus. Set a variable like this:
BOOL bRunGame;
Then replace your loop in WinMain with something more like this:
MSG msg
for( ;; )
{
if( PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE) )
{
// Then we have a message waiting...
if( msg.message == WM_QUIT )
break; // only way to exit the loop
GetMessage(&msg, NULL, 0, 0);
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else
{
// The game function.
if( bRunGame )
RunGame();
}
}
return msg.wParam;
There's probably a better way to write that loop...but I'm running out of breath...just set the bRunGame flag to either TRUE or FALSE in your WndProc...then your game shouldn't hog resources.
Good Luck!
- null_pointer
Edited by - null_pointer on 2/3/00 1:14:54 PM
Edited by - null_pointer on 2/3/00 1:21:41 PM
You need a very different "buffering system" in windowed mode. You can't create attached back buffers and flip() them, because you don't have exclusive access to the video hardware. (what if other windowed mode apps tried to flip at the same time?) Also, you don't own the back buffer memory --> every app would have to share it...
The system you need in windowed 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.dwFlags = 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
Note that you did NOT use the DDSCAPS_BACKBUFFER flag in there -- that's only for fullscreen mode apps. Instead of flipping, you call Blt() from the primary surface, specifying the back buffer as the source surface:
lpddsPrimary->Blt(...lpddsBack...);
Finally, 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...);
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.
I'd recommend making an InitDirectDraw(bool) function that handles both (hence the bool flag), and a FreeDirectDraw(bool) that handles both too. You might as well destroy everything and create it from scratch when you switch between windowed and fullscreen, because the differences are largely in these two functions. Also, you can't set the display mode in windowed mode, but you can get the display mode. Use GetSystemMetrics(SM_CXSCREEN) (and SM_CYSCREEN) to determine the width and height. There's also a parameter that you can use in that function to get the bit depth --> you'll need that for loading bitmaps and modifying surfaces.
Finally, switching between modes isn't to hard. DON'T track something like ALT+TAB. It's bad coding...what if Windows came up with another method of switching apps? You code would crash every time. Instead, handle the WM_ACTIVATE message in your WndProc. It's simple: one of the parameters is a flag that tells you whether or not the window is being activated, or not. Simply call FreeDirectDraw(...) and InitDirectDraw(...) with the right flag and you're set. Add the lpDD->RestoreAllSurfaces() call to WndProc, or maybe to your drawing functions.
I know I said finally in the last paragraph, but there's one more point: you can't hog the system anymore. You have to give it back to Windows when the user goes to another window. How? Use the other parameter of the WM_ACTIVATE message (simple, huh?) It tells you when your app is either getting or losing the focus. Set a variable like this:
BOOL bRunGame;
Then replace your loop in WinMain with something more like this:
MSG msg
for( ;; )
{
if( PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE) )
{
// Then we have a message waiting...
if( msg.message == WM_QUIT )
break; // only way to exit the loop
GetMessage(&msg, NULL, 0, 0);
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else
{
// The game function.
if( bRunGame )
RunGame();
}
}
return msg.wParam;
There's probably a better way to write that loop...but I'm running out of breath...just set the bRunGame flag to either TRUE or FALSE in your WndProc...then your game shouldn't hog resources.
Good Luck!
- null_pointer
Edited by - null_pointer on 2/3/00 1:14:54 PM
Edited by - null_pointer on 2/3/00 1:21:41 PM
Great, thanks! Between that and screwing wroung in MSVC''s help file I''ve got it running! I''ve noticed one thing that''s a problem though. Right now the program just displays a random number in the middle of the window. However, when I move the window to (rough estimate) 300 pixels above the bottom of the screen or 200 pixels from the right of the screen, the numbers cease to display until I move it back to the left/top (It''s a 640x480 window and I run in 1024x768). I believe I''ve attached the clipper correctly...do you have an idea as to what''s wrong? Here''s the basic code I am using:
int i=rand();
char suck[30];
RECT work=render.getRect();
sprintf(suck,"This is a test... %i",i); ClientToScreen(g_hWnd,(LPPOINT)&work);
render.DrawFont(NULL,suck, work.left+200, work.top+300);
// Flip the surfaces
if(render.flip()==FALSE) return FALSE;
return TRUE;
getRect() returns a RECT structure that contains the coordinates of the window, like the Win32 GetClientRect function. Any ideas? Thank you, whatever happens!
Anthracks
int i=rand();
char suck[30];
RECT work=render.getRect();
sprintf(suck,"This is a test... %i",i); ClientToScreen(g_hWnd,(LPPOINT)&work);
render.DrawFont(NULL,suck, work.left+200, work.top+300);
// Flip the surfaces
if(render.flip()==FALSE) return FALSE;
return TRUE;
getRect() returns a RECT structure that contains the coordinates of the window, like the Win32 GetClientRect function. Any ideas? Thank you, whatever happens!

Anthracks
Sorry, haven't been here in a while...anyway, I ran into the same problem too (and it's now solved).
First thing to remember in DirectDraw is: the primary surface (a.k.a. the front buffer) is the screen. It's always there. The video card refreshes the screen so many times per second, and that's the memory it uses. When you create a DirectDraw primary surface, you never actually create memory, you're just getting a pointer to something that controls it. The memory was always there, already being used.
In the case of windowed mode, that means there was a primary surface before DirectDraw even loaded. That's what Windows displays all the time. So, you're just accessing shared memory that all the other applications use (indirectly, via the Windows GUI). It doesn't belong to your window in any way, like it seems to be in fullscreen mode. (Incidentally, in fullscreen mode, your window does take up the whole screen, so in effect the primary surface belongs to your window.)
Here's what is happening to you:
In the regular Windows GUI, the default action for all drawing functions is to draw relative to the client area of the window (the white part, like in Windows Notepad). In DirectDraw, the primary surface is the whole screen, so you're always drawing relative to the upper left corner, which is 0, 0.
So, you're blitting the number at a point on the screen, then when you move your window, the clipper hides the number. That's because the number stays at the same point on the screen (which is now outside the window). The idea is to get the current rect of your window each frame, then blit to that location. (If you want the menu text, status bars, etc. of your window to appear, you have to keep from overwriting them too.)
I'll just post my code here directly. It blits the back buffer to the front buffer, at the location specified by the first LPRECT in the lpDD->Blt() function. (You can do a lot of neat tricks with those RECTs, BTW.)
void UpdateDisplay()
{
// Get the window position and size.
RECT rect;
GetWindowRect(hMainWnd, ▭);
// Restore the surfaces that are lost.
DirectDraw.lpDD->RestoreAllSurfaces();
// Clear the render surface.
ClearSurface(DirectDraw.lpRenderSurface);
// Display it in the window.
DirectDraw.lpDisplaySurface->Blt(▭, DirectDraw.lpRenderSurface, NULL, DDBLT_WAIT, NULL);
}
I was doing GetClientRect() and then calling ScreenToClient() with the two points that make up the RECT, but GetWindowRect() does essentially the same thing. Oh, and the DirectDraw struct is just something I defined like this to organize things a bit:
struct {
LPDIRECTDRAW7 lpDD;
LPDIRECTDRAWSURFACE7 lpDisplaySurface;
LPDIRECTDRAWSURFACE7 lpRenderSurface;
LPDIRECTDRAWCLIPPER lpClipper;
} DirectDraw;
You might be able to restore the surfaces in your WndProc in response to a WM_ACTIVATE message, instead of doing it each frame. Your surfaces would be probably only be "lost" when another app gains the focus, in windowed mode, so just lpDD->RestoreAllSurfaces() when you get the focus back again. If you get garbage, you might try something different...just theory again...
Finally, for performance reasons, you might want to keep your window's client area the same size as your back buffer. Keep the back buffer's size something constant, like the same size you'd probably use in fullscreen mode (640x480, 800x600, 1024x768, but NOT 576x298). That way you don't have to worry about using StretchBlt() at all. Besides, your fullscreen mode drawing code won't need to be changed. The back buffer will be limited to certain dimensions. However, when you create a window, typically using CreateWindow or CreateWindowEx, that supplies the dimensions to be used for the entire window, including the title bar, menus, etc.
To set the size of the client RECT to the desired size, first determine the size of the window's client RECT (via GetClientRect). Then get the window's actual size (via GetWindowRect). Lastly, increase the window's actual size (via SetWindowRect) by the difference between the size you want (640x480, 800x600, etc.) and the current size of the client rect (maybe 619x476 ?? ). So, you're making the window's client RECT equal to the size you want, regardless of the height of the title bar, the size of the borders, etc. Important stuff, considering how many Windows "schemes" there are floating around...
Good Luck!
- null_pointer
Edited by - null_pointer on 2/7/00 3:48:21 PM
First thing to remember in DirectDraw is: the primary surface (a.k.a. the front buffer) is the screen. It's always there. The video card refreshes the screen so many times per second, and that's the memory it uses. When you create a DirectDraw primary surface, you never actually create memory, you're just getting a pointer to something that controls it. The memory was always there, already being used.
In the case of windowed mode, that means there was a primary surface before DirectDraw even loaded. That's what Windows displays all the time. So, you're just accessing shared memory that all the other applications use (indirectly, via the Windows GUI). It doesn't belong to your window in any way, like it seems to be in fullscreen mode. (Incidentally, in fullscreen mode, your window does take up the whole screen, so in effect the primary surface belongs to your window.)
Here's what is happening to you:
In the regular Windows GUI, the default action for all drawing functions is to draw relative to the client area of the window (the white part, like in Windows Notepad). In DirectDraw, the primary surface is the whole screen, so you're always drawing relative to the upper left corner, which is 0, 0.
So, you're blitting the number at a point on the screen, then when you move your window, the clipper hides the number. That's because the number stays at the same point on the screen (which is now outside the window). The idea is to get the current rect of your window each frame, then blit to that location. (If you want the menu text, status bars, etc. of your window to appear, you have to keep from overwriting them too.)
I'll just post my code here directly. It blits the back buffer to the front buffer, at the location specified by the first LPRECT in the lpDD->Blt() function. (You can do a lot of neat tricks with those RECTs, BTW.)
void UpdateDisplay()
{
// Get the window position and size.
RECT rect;
GetWindowRect(hMainWnd, ▭);
// Restore the surfaces that are lost.
DirectDraw.lpDD->RestoreAllSurfaces();
// Clear the render surface.
ClearSurface(DirectDraw.lpRenderSurface);
// Display it in the window.
DirectDraw.lpDisplaySurface->Blt(▭, DirectDraw.lpRenderSurface, NULL, DDBLT_WAIT, NULL);
}
I was doing GetClientRect() and then calling ScreenToClient() with the two points that make up the RECT, but GetWindowRect() does essentially the same thing. Oh, and the DirectDraw struct is just something I defined like this to organize things a bit:
struct {
LPDIRECTDRAW7 lpDD;
LPDIRECTDRAWSURFACE7 lpDisplaySurface;
LPDIRECTDRAWSURFACE7 lpRenderSurface;
LPDIRECTDRAWCLIPPER lpClipper;
} DirectDraw;
You might be able to restore the surfaces in your WndProc in response to a WM_ACTIVATE message, instead of doing it each frame. Your surfaces would be probably only be "lost" when another app gains the focus, in windowed mode, so just lpDD->RestoreAllSurfaces() when you get the focus back again. If you get garbage, you might try something different...just theory again...
Finally, for performance reasons, you might want to keep your window's client area the same size as your back buffer. Keep the back buffer's size something constant, like the same size you'd probably use in fullscreen mode (640x480, 800x600, 1024x768, but NOT 576x298). That way you don't have to worry about using StretchBlt() at all. Besides, your fullscreen mode drawing code won't need to be changed. The back buffer will be limited to certain dimensions. However, when you create a window, typically using CreateWindow or CreateWindowEx, that supplies the dimensions to be used for the entire window, including the title bar, menus, etc.
To set the size of the client RECT to the desired size, first determine the size of the window's client RECT (via GetClientRect). Then get the window's actual size (via GetWindowRect). Lastly, increase the window's actual size (via SetWindowRect) by the difference between the size you want (640x480, 800x600, etc.) and the current size of the client rect (maybe 619x476 ?? ). So, you're making the window's client RECT equal to the size you want, regardless of the height of the title bar, the size of the borders, etc. Important stuff, considering how many Windows "schemes" there are floating around...
Good Luck!
- null_pointer
Edited by - null_pointer on 2/7/00 3:48:21 PM
This topic is closed to new replies.
Advertisement
Popular Topics
Advertisement
Recommended Tutorials
Advertisement