1 2 3 4 5 6 7
Setting up your Compiler Our Generic Windows Skeleton A Simple Cube Matrices Wazoo of Borg FullScreen Ahead Mr. Crusher On screen. Magnify.

 

 

 

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.
//This structure defines a display mode type for our adapter
struct D3DModeInfo
{
    DWORD      Width;
    DWORD      Height;
    D3DFORMAT  Format;
    DWORD      dwBehavior;
    D3DFORMAT  DepthStencilFormat;
};

//This structure defines information about a device
struct D3DDeviceInfo
{
    D3DDEVTYPE	DeviceType;
    D3DCAPS8	d3dCaps;
    char		szDesc[32];
    BOOL		bCanDoWindowed;
    DWORD		dwNumModes;
    D3DModeInfo	modes[150];
    DWORD		dwCurrentMode;
    BOOL		bWindowed;
    D3DMULTISAMPLE_TYPE MultiSampleType;
};

//This structure defines a graphics adapter in our machine
//Note: for the most part, people will only have ONE adapter
//      but just for completion's sake, we can check for more
struct D3DAdapterInfo
{
    D3DADAPTER_IDENTIFIER8 d3dAdapterIdentifier;
    D3DDISPLAYMODE d3ddmDesktop;
    DWORD          dwNumDevices;
    D3DDeviceInfo  devices[5];
    DWORD          dwCurrentDevice;
};

D3DAdapterInfo  g_Adapters[10];       //Let's store 10 possible adapters
DWORD           g_dwNumAdapters   = 0;//Variable to store total number of adapters in the system
DWORD           g_dwAdapter       = 0;//Variable to store closest adapter containing the device mode that we need
BOOL            g_bUseDepthBuffer = TRUE;//Flag for defining if our chosen adapter supports the depth (ZBuffer)
BOOL            g_bWindowed       = TRUE; //Flag for defining if our chosen adapter will function in windowed
	                                  //or fullscreen mode

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++ ) {
		// Fill in adapter info
		pAdapter  = &g_Adapters[g_dwNumAdapters];
		g_pD3D->GetAdapterDisplayMode( iAdapter, &pAdapter->d3ddmDesktop );
		pAdapter->dwNumDevices    = 0;
		pAdapter->dwCurrentDevice = 0;

		// Enumerate all display modes on this adapter
		dwNumFormats      = 0;
		dwNumModes        = 0;
		dwNumAdapterModes = g_pD3D->GetAdapterModeCount( iAdapter );

		// Add the adapter's current desktop format to the list of formats
		formats[dwNumFormats++] = pAdapter->d3ddmDesktop.Format;

		// Loop through modes
		for( iMode = 0; iMode < dwNumAdapterModes; iMode++ ) {
			// Get the display mode attributes
			g_pD3D->EnumAdapterModes( iAdapter, iMode, &DisplayMode );

			// Filter out low-resolution modes
			if( (DisplayMode.Width < g_dwDeviceWidth) || (DisplayMode.Height < g_dwDeviceHeight) )
				continue;

			// Check if the mode already exists
			for( m = 0; m < dwNumModes; m++ ) {
				if( ( modes[m].Width  == DisplayMode.Width  ) &&
					( modes[m].Height == DisplayMode.Height ) &&
					( modes[m].Format == DisplayMode.Format ) )
					break;
			}

			// If we found a new mode, add it to the list of modes
			if( m == dwNumModes ) {
				modes[dwNumModes].Width       = DisplayMode.Width;
				modes[dwNumModes].Height      = DisplayMode.Height;
				modes[dwNumModes].Format      = DisplayMode.Format;
				modes[dwNumModes].RefreshRate = 0;
				dwNumModes++;
				// Check if the mode's format already exists
				for( f = 0; f < dwNumFormats; f++ ) {
					if( DisplayMode.Format == formats[f] )
						break;
				}
				// If the format is new, add it to the list
				if( f == dwNumFormats )
					formats[dwNumFormats++] = DisplayMode.Format;
			}
		}

		// Add devices to adapter
		for( iDevice = 0; iDevice < dwNumDeviceTypes; iDevice++ ) {
			// Fill in device info
			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;

			// Check each format to see if it is compatible with the application
			for( f = 0; f < dwNumFormats; f++ ) {
				bFormatConfirmed[f] = FALSE;
				fmtDepthStencil[f] = D3DFMT_UNKNOWN;

				// Skip formats that cannot be used as render targets on this device
				if( FAILED( g_pD3D->CheckDeviceType( iAdapter, pDevice->DeviceType, formats[f], formats[f], FALSE ) ) )
					continue;

				if( pDevice->DeviceType == D3DDEVTYPE_HAL ) {
					// This system has a HAL device
					bHALExists = TRUE;
					// Check if Windowed mode is possible
					if( pDevice->d3dCaps.Caps2 & D3DCAPS2_CANRENDERWINDOWED ) {
						// HAL can run in a window for some mode
						bHALIsWindowedCompatible = TRUE;

						if( f == 0 ) {
							// HAL can run in a window for the current desktop mode
							bHALIsDesktopCompatible = TRUE;
						}
					}
				}
				// Confirm the device/format for HW vertex processing
				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;
					}
				}

				// Confirm the device/format for SW vertex processing
				if( FALSE == bFormatConfirmed[f] ) {
					dwBehavior[f] = D3DCREATE_SOFTWARE_VERTEXPROCESSING;

					if( SUCCEEDED( confirmDevice( &pDevice->d3dCaps, dwBehavior[f], formats[f] ) ) )
						bFormatConfirmed[f] = TRUE;
				}

				// Find a suitable depth/stencil buffer format for this device/format
				if( bFormatConfirmed[f] && g_bUseDepthBuffer ) {
					if( !findDepthStencilFormat( 8, 0, iAdapter, pDevice->DeviceType,
						formats[f], &fmtDepthStencil[f] ) ) {
						bFormatConfirmed[f] = FALSE;
					}
				}
			}

			// Add modes that are confirmed to the list of available modes
			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;
						}
					}
				}
			}

			// Check if there was a device found that matched desired requirements
			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;
					}
				}
			}

			// Check if device can work in a window
			if( bFormatConfirmed[0] && (pDevice->d3dCaps.Caps2 & D3DCAPS2_CANRENDERWINDOWED) ) {
				pDevice->bCanDoWindowed = TRUE;
				pDevice->bWindowed      = TRUE;
			}

			// If valid modes were found, keep this device
			if( pDevice->dwNumModes > 0 )
				pAdapter->dwNumDevices++;
		}
		// If valid devices were found, keep this adapter
		if( pAdapter->dwNumDevices > 0 )
			g_dwNumAdapters++;
	}

	// Return an error if no compatible devices were found
	if( !g_dwNumAdapters || !bFoundVidMode )
		return( E_FAIL );

	// Choose a default adapter
	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;

				// Display a warning message
				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)
{

	// Check for alpha support
	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]

 
 
© 2003, Silver.
Wind, two Tears, and a Funeral...