|
Tutorial #6: FullScreen Ahead Mr. Crusher
Erik Yuzwa
Version History
Date |
Version |
Description |
Font Color |
Sept. 11th, 2001 |
1.0 |
General Public Release |
White |
Well here we are at Direct3D tutorial #4. Well know that we've
gotten pretty far into Direct3D programming, I've decided that
we've got one thing missing. We don't as yet know how
to present our material in fullscreen mode! That is a mistake
which we will rectify at this very moment.
We'll first create a new project, using the steps from our Tutorial #1
Okay we are going to be working from our new Windows skeleton
here so make sure you back up your original and only modify
a copy..making it easier to reuse the skeleton in later applications.
Just to let everyone know that we're on the same page, I'll
post the modifications that we made to the WIN32 skeleton, to
make it a bit easier to handle fullscreen and/or windowed mode
in Direct3D applications. The new changes will be in blue text.
loadConfigSettings("WazooGame.ini");
DWORD dwStyle;
DWORD dwExStyle;
RECT windowRect;
if(g_bIsFullScreen){
windowRect.left = (long)0;
windowRect.right = (long)g_dwDeviceWidth;
windowRect.top = (long)0;
windowRect.bottom = (long)g_dwDeviceHeight;
}else{
windowRect.left = (long)0;
windowRect.right = (long)g_dwAppWidth;
windowRect.top = (long)0;
windowRect.bottom = (long)g_dwAppHeight;
}
if(g_bIsFullScreen){
DEVMODE dmScreenSettings;
ZeroMemory(&dmScreenSettings, sizeof(dmScreenSettings));
dmScreenSettings.dmSize = sizeof(dmScreenSettings);
dmScreenSettings.dmPelsWidth = (int)g_dwDeviceWidth;
dmScreenSettings.dmPelsHeight = (int)g_dwDeviceHeight;
dmScreenSettings.dmBitsPerPel = (int)g_dwAppDepth;
dmScreenSettings.dmFields = DM_BITSPERPEL |
DM_PELSWIDTH |
DM_PELSHEIGHT;
long ret = ChangeDisplaySettings(&dmScreenSettings,
CDS_FULLSCREEN);
if(ret != DISP_CHANGE_SUCCESSFUL){
g_bIsFullScreen = FALSE;
}
}
if(g_bIsFullScreen){
dwExStyle = WS_EX_APPWINDOW;
dwStyle = WS_POPUP;
ShowCursor(FALSE);
}else{
dwExStyle = WS_EX_APPWINDOW |
WS_EX_WINDOWEDGE;
dwStyle = WS_OVERLAPPEDWINDOW;
}
AdjustWindowRectEx(&windowRect,
dwStyle,
FALSE,
dwExStyle);
// create the window, note the use of WS_POPUP
if (!(hWnd = CreateWindowEx(NULL,
g_szAppClass, // class
g_szAppTitle, // title
dwStyle |
WS_CLIPCHILDREN |
WS_CLIPSIBLINGS,
0,0, // x,y
windowRect.right - windowRect.left, // width
windowRect.bottom - windowRect.top, // height
NULL, // handle to parent
NULL, // handle to menu
hInstance,// instance
NULL))) // creation parms
return(0);
So that's the changes that we made to our winmain file,
which is reflected in our new WIN32 skeleton
file.
Well unlike our OpenGL fullscreen
tutorial, which was a relative snap, we are not quite done
yet with Direct3D. There's a whole bunch of new code that we
suddenly have to worry about, but will help us in the
long run with future applications...(trust me).
First, we're going to have to build a list of devices attached
to our system. It's just quite possible that somebody out there
running your application will have more than one video card
attached to the system, and we want to make sure that we're
using the correct one, rather than whatever's left in as the
default card.
To begin with, at the top of our code file, let's add a few
new global variables.
struct D3DModeInfo
{
DWORD Width;
DWORD Height;
D3DFORMAT Format;
DWORD dwBehavior;
D3DFORMAT DepthStencilFormat;
};
struct D3DDeviceInfo
{
D3DDEVTYPE DeviceType;
D3DCAPS8 d3dCaps;
char szDesc[32];
BOOL bCanDoWindowed;
DWORD dwNumModes;
D3DModeInfo modes[150];
DWORD dwCurrentMode;
BOOL bWindowed;
D3DMULTISAMPLE_TYPE MultiSampleType;
};
struct D3DAdapterInfo
{
D3DADAPTER_IDENTIFIER8 d3dAdapterIdentifier;
D3DDISPLAYMODE d3ddmDesktop;
DWORD dwNumDevices;
D3DDeviceInfo devices[5];
DWORD dwCurrentDevice;
};
D3DAdapterInfo g_Adapters[10];
DWORD g_dwNumAdapters = 0;
DWORD g_dwAdapter = 0;
BOOL g_bUseDepthBuffer = TRUE;
BOOL g_bWindowed = TRUE;
Nothing too fancy here right? we're simply defining some structures
which will hold our device information.
Now we'll define our function which cycles through and builds
our main device list. I want to place a warning right here,
that the following code is rather..erm
long
so don't hesitate to take some occasional breaks. I wanted to
keep the function intact, so that you could read through it
and digest what's happening. I'll add in appropriate comments
in some of the more wtf? areas, so hopefully this will
clear up any confusion.
HRESULT buildDeviceList()
{
const DWORD dwNumDeviceTypes = 2;
const char *szDeviceDescs[] = { "HAL", "REF" };
const D3DDEVTYPE DeviceTypes[] = { D3DDEVTYPE_HAL, D3DDEVTYPE_REF };
D3DDISPLAYMODE modes[100];
D3DFORMAT formats[20];
BOOL bFormatConfirmed[20];
DWORD dwBehavior[20];
D3DFORMAT fmtDepthStencil[20];
D3DAdapterInfo *pAdapter;
BOOL bHALExists = FALSE;
BOOL bHALIsWindowedCompatible = FALSE;
BOOL bHALIsDesktopCompatible = FALSE;
BOOL bHALIsSampleCompatible = FALSE;
DWORD dwNumFormats;
DWORD dwNumModes;
DWORD dwNumAdapterModes;
unsigned int iAdapter,iMode,iDevice;
DWORD a,d,f,m;
D3DDISPLAYMODE DisplayMode;
BOOL bFoundVidMode = FALSE;
D3DDeviceInfo *pDevice;
for( iAdapter = 0; iAdapter < g_pD3D->GetAdapterCount(); iAdapter++ ) {
pAdapter = &g_Adapters[g_dwNumAdapters];
g_pD3D->GetAdapterDisplayMode( iAdapter, &pAdapter->d3ddmDesktop );
pAdapter->dwNumDevices = 0;
pAdapter->dwCurrentDevice = 0;
dwNumFormats = 0;
dwNumModes = 0;
dwNumAdapterModes = g_pD3D->GetAdapterModeCount( iAdapter );
formats[dwNumFormats++] = pAdapter->d3ddmDesktop.Format;
for( iMode = 0; iMode < dwNumAdapterModes; iMode++ ) {
// Get the display mode attributes
g_pD3D->EnumAdapterModes( iAdapter, iMode, &DisplayMode );
if( (DisplayMode.Width < g_dwDeviceWidth) || (DisplayMode.Height < g_dwDeviceHeight) )
continue;
for( m = 0; m < dwNumModes; m++ ) {
if( ( modes[m].Width == DisplayMode.Width ) &&
( modes[m].Height == DisplayMode.Height ) &&
( modes[m].Format == DisplayMode.Format ) )
break;
}
if( m == dwNumModes ) {
modes[dwNumModes].Width = DisplayMode.Width;
modes[dwNumModes].Height = DisplayMode.Height;
modes[dwNumModes].Format = DisplayMode.Format;
modes[dwNumModes].RefreshRate = 0;
dwNumModes++;
for( f = 0; f < dwNumFormats; f++ ) {
if( DisplayMode.Format == formats[f] )
break;
}
if( f == dwNumFormats )
formats[dwNumFormats++] = DisplayMode.Format;
}
}
for( iDevice = 0; iDevice < dwNumDeviceTypes; iDevice++ ) {
pDevice = &pAdapter->devices[pAdapter->dwNumDevices];
pDevice->DeviceType = DeviceTypes[iDevice];
g_pD3D->GetDeviceCaps( iAdapter, DeviceTypes[iDevice], &pDevice->d3dCaps );
strcpy(pDevice->szDesc,szDeviceDescs[iDevice]);
pDevice->dwNumModes = 0;
pDevice->dwCurrentMode = 0;
pDevice->bCanDoWindowed = FALSE;
pDevice->bWindowed = FALSE;
pDevice->MultiSampleType = D3DMULTISAMPLE_NONE;
for( f = 0; f < dwNumFormats; f++ ) {
bFormatConfirmed[f] = FALSE;
fmtDepthStencil[f] = D3DFMT_UNKNOWN;
if( FAILED( g_pD3D->CheckDeviceType( iAdapter, pDevice->DeviceType, formats[f], formats[f], FALSE ) ) )
continue;
if( pDevice->DeviceType == D3DDEVTYPE_HAL ) {
bHALExists = TRUE;
if( pDevice->d3dCaps.Caps2 & D3DCAPS2_CANRENDERWINDOWED ) {
bHALIsWindowedCompatible = TRUE;
if( f == 0 ) {
bHALIsDesktopCompatible = TRUE;
}
}
}
if( pDevice->d3dCaps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT ) {
if( pDevice->d3dCaps.DevCaps & D3DDEVCAPS_PUREDEVICE ) {
dwBehavior[f] = D3DCREATE_HARDWARE_VERTEXPROCESSING | D3DCREATE_PUREDEVICE;
if( SUCCEEDED( confirmDevice( &pDevice->d3dCaps, dwBehavior[f], formats[f] ) ) )
bFormatConfirmed[f] = TRUE;
}
if ( FALSE == bFormatConfirmed[f] ) {
dwBehavior[f] = D3DCREATE_HARDWARE_VERTEXPROCESSING;
if( SUCCEEDED( confirmDevice( &pDevice->d3dCaps, dwBehavior[f], formats[f] ) ) )
bFormatConfirmed[f] = TRUE;
}
if ( FALSE == bFormatConfirmed[f] ) {
dwBehavior[f] = D3DCREATE_MIXED_VERTEXPROCESSING;
if( SUCCEEDED( confirmDevice( &pDevice->d3dCaps, dwBehavior[f], formats[f] ) ) )
bFormatConfirmed[f] = TRUE;
}
}
if( FALSE == bFormatConfirmed[f] ) {
dwBehavior[f] = D3DCREATE_SOFTWARE_VERTEXPROCESSING;
if( SUCCEEDED( confirmDevice( &pDevice->d3dCaps, dwBehavior[f], formats[f] ) ) )
bFormatConfirmed[f] = TRUE;
}
if( bFormatConfirmed[f] && g_bUseDepthBuffer ) {
if( !findDepthStencilFormat( 8, 0, iAdapter, pDevice->DeviceType,
formats[f], &fmtDepthStencil[f] ) ) {
bFormatConfirmed[f] = FALSE;
}
}
}
for( m = 0; m < dwNumModes; m++ ) {
for( f = 0; f < dwNumFormats; f++ ) {
if( modes[m].Format == formats[f] ) {
if( bFormatConfirmed[f] == TRUE ) {
// Add this mode to the devices list of valid modes
pDevice->modes[pDevice->dwNumModes].Width = modes[m].Width;
pDevice->modes[pDevice->dwNumModes].Height = modes[m].Height;
pDevice->modes[pDevice->dwNumModes].Format = modes[m].Format;
pDevice->modes[pDevice->dwNumModes].dwBehavior = dwBehavior[f];
pDevice->modes[pDevice->dwNumModes].DepthStencilFormat = fmtDepthStencil[f];
pDevice->dwNumModes++;
if( pDevice->DeviceType == D3DDEVTYPE_HAL )
bHALIsSampleCompatible = TRUE;
}
}
}
}
for( m = 0; m < pDevice->dwNumModes; m++ ) {
if( pDevice->modes[m].Width == g_dwDeviceWidth && pDevice->modes[m].Height == g_dwDeviceHeight ) {
pDevice->dwCurrentMode = m;
if( g_bWindowed ) {
if( pDevice->modes[m].Format == pAdapter->d3ddmDesktop.Format ) {
bFoundVidMode = TRUE;
break;
}
}
else if( pDevice->modes[m].Format == D3DFMT_R5G6B5 ||
pDevice->modes[m].Format == D3DFMT_X1R5G5B5 ||
pDevice->modes[m].Format == D3DFMT_A1R5G5B5 ||
pDevice->modes[m].Format == D3DFMT_X8R8G8B8 ||
pDevice->modes[m].Format == D3DFMT_A8R8G8B8 ) {
bFoundVidMode = TRUE;
break;
}
}
}
if( bFormatConfirmed[0] && (pDevice->d3dCaps.Caps2 & D3DCAPS2_CANRENDERWINDOWED) ) {
pDevice->bCanDoWindowed = TRUE;
pDevice->bWindowed = TRUE;
}
if( pDevice->dwNumModes > 0 )
pAdapter->dwNumDevices++;
}
if( pAdapter->dwNumDevices > 0 )
g_dwNumAdapters++;
}
if( !g_dwNumAdapters || !bFoundVidMode )
return( E_FAIL );
for( a = 0; a < g_dwNumAdapters; a++ ) {
for( d = 0; d < g_Adapters[a].dwNumDevices; d++ ) {
if( (g_Adapters[a].devices[d].bWindowed && g_bWindowed) || (!g_bWindowed) ) {
g_Adapters[a].dwCurrentDevice = d;
g_dwAdapter = a;
g_Adapters[a].devices[d].bWindowed = g_bWindowed;
if( g_Adapters[a].devices[d].DeviceType == D3DDEVTYPE_REF ) {
if( !bHALExists )
MessageBox( NULL, "HAL Error", "Hardware Acceleration Not Found.", MB_ICONERROR );
else if( !bHALIsSampleCompatible )
MessageBox( NULL, "HAL Error", "Your Hardware Acceleration Cannot Support This Application.", MB_ICONERROR );
else if( !bHALIsWindowedCompatible && !g_bWindowed )
MessageBox( NULL, "HAL Error", "Your Hardware Acceleration Cannot Render To A Window.", MB_ICONERROR );
else if( !bHALIsDesktopCompatible && !g_bWindowed )
MessageBox( NULL, "HAL Error", "Your Hardware Acceleration Cannot Render To A Window In The Current Desktop Setting.", MB_ICONERROR );
else
MessageBox( NULL, "HAL Error", "Your Hardware Acceleration Cannot Support This Application In The Current Desktop Setting.", MB_ICONERROR );
}
return S_OK;
}
}
}
return( E_FAIL );
}
I know that function was REALLY really....REALLY long, but I
thought for continuity, I'd leave in the entire thing without
breaking it up. What we're essentially doing, is looping through
all of the graphics adapters found on the current computer.
We're then going through EACH one individually, and first checking
if the resolution supported is smaller than our desired resolution
in our .ini file. If it is, then we skip it (as there's no need
to go through the trouble of checking if it's a valid format).
If we can use it, we go through the device attributes
of the chosen graphics adapter to see what is supported and
if this is in fact the one we want to use for our application.
The next step in our code, is to confirm that we can use the
current adapter for hardware vertex processing. That is what
the following function will do for us.
HRESULT confirmDevice(D3DCAPS8* pCaps,
DWORD dwBehavior,
D3DFORMAT Format)
{
if( pCaps->TextureCaps & D3DPTEXTURECAPS_ALPHAPALETTE )
return S_OK;
if( pCaps->TextureCaps & D3DPTEXTURECAPS_ALPHA )
return S_OK;
return E_FAIL;
}
All we're doing in the small function above, is seeing if our
selected graphics adapter supports alpha blending.
In our next function that we're putting together, we need to
find out what D3DFORMAT we need to specify as our DepthStencil
in our CreateDevice function. The function below
will do that for us. It will also validate for us if the format
chosen is supported by our graphics adapter.
BOOL findDepthStencilFormat( DWORD dwMinDepth,
DWORD dwMinStencil,
UINT iAdapter,
D3DDEVTYPE DeviceType,
D3DFORMAT TargetFormat,
D3DFORMAT* pDepthStencilFormat ) {
D3DFORMAT d3dfFormat = D3DFMT_UNKNOWN;
if( dwMinDepth <= 16 && dwMinStencil == 0 ) {
d3dfFormat = D3DFMT_D16;
if( (validateFormat(d3dfFormat,
iAdapter,
DeviceType,
TargetFormat)) == TRUE ) {
*pDepthStencilFormat = d3dfFormat;
return(TRUE);
}
}
if( dwMinDepth <= 15 && dwMinStencil <= 1 ) {
d3dfFormat = D3DFMT_D15S1;
if( (validateFormat(d3dfFormat,
iAdapter,
DeviceType,
TargetFormat)) == TRUE ) {
*pDepthStencilFormat = d3dfFormat;
return(TRUE);
}
}
if( dwMinDepth <= 24 && dwMinStencil == 0 ) {
d3dfFormat = D3DFMT_D24X8;
if( (validateFormat(d3dfFormat,
iAdapter,
DeviceType,
TargetFormat)) == TRUE ) {
*pDepthStencilFormat = d3dfFormat;
return(TRUE);
}
}
if( dwMinDepth <= 24 && dwMinStencil <= 8 ) {
d3dfFormat = D3DFMT_D24S8;
if( (validateFormat(d3dfFormat,
iAdapter,
DeviceType,
TargetFormat)) == TRUE ) {
*pDepthStencilFormat = d3dfFormat;
return(TRUE);
}
}
if( dwMinDepth <= 24 && dwMinStencil <= 4 ) {
d3dfFormat = D3DFMT_D24X4S4;
if( (validateFormat(d3dfFormat,
iAdapter,
DeviceType,
TargetFormat)) == TRUE ) {
*pDepthStencilFormat = d3dfFormat;
return(TRUE);
}
}
if( dwMinDepth <= 32 && dwMinStencil == 0 ) {
d3dfFormat = D3DFMT_D32;
if( (validateFormat(d3dfFormat,
iAdapter,
DeviceType,
TargetFormat)) == TRUE ) {
*pDepthStencilFormat = d3dfFormat;
return(TRUE);
}
}
return FALSE;
}
Okay that was horrible. *grin*. This function wasn't all that
large, but it does do a lot of the grunt work for us. We're
filtering out what kind of D3DFORMAT we are using and are passing
it on to our validateFormat function defined below.
If we've found a format that our graphics adapter supports and
one which matches our desired settings in our .ini file, then
we return TRUE!
Well our next and last function that we have to worry about
(for the moment) for Direct3D programming, is validating our
BackBuffer Format. We just want to make sure that the D3DFORMAT
we've temporarily selected is handled by our graphics adapter
device. We don't have to do anything really special here, just
check with our LPDIRECT3D object to see if our D3DFORMAT is
supported and to make sure we've got the correct DepthStencil.
BOOL validateFormat(D3DFORMAT d3dfFormatToTest,
UINT iAdapter,
D3DDEVTYPE DeviceType,
D3DFORMAT TargetFormat)
{
HRESULT hr;
hr = g_pD3D->CheckDeviceFormat( iAdapter,
DeviceType,
TargetFormat,
D3DUSAGE_DEPTHSTENCIL,
D3DRTYPE_SURFACE,
d3dfFormatToTest );
if( SUCCEEDED( hr ) ) {
hr = g_pD3D->CheckDepthStencilMatch( iAdapter,
DeviceType,
TargetFormat,
TargetFormat,
d3dfFormatToTest );
if( SUCCEEDED( hr ) ) {
return TRUE;
}
}
return FALSE;
}
And that's about it for today's lesson!
Phew! Well that was an excruciating amount of work just to get
our 3d card to work in fullscreen mode! Holy function calls
Batman!
Well the good news is that you only need to write all this stuff
once! You can reuse this entire code in any of your Direct3D
applications, which will in turn cut down development time (hopefully).
Take a breather, and relax a bit to digest all of this information.
Come back later to check out the next tutorial, but for now
take it easy.
If you'd like more material to look at, I'd suggest the SDK
samples that came with the DirectX SDK. That's where I pulled
a lot of this material from, and just made some modifications
based on our application needs. The MSDN
is also a pretty good place to try as well. Or for you literary
types, I've found a lot of good information with Direct3D programming
in Programming Multiplayer Games with DirectX
published by Prima Publishing.
Back
To Index
[top] |
|
|