The purpose of this article is to explain a method of enumeration in DirectX 7. If you are developing a Direct Draw application, the Driver and Display Mode enumeration areas are relevant. If you are developing a Direct 3D application, all areas are relevant.
A Driver can be thought of as a video card. Enumerating the Drivers is equivalent to determining what video cards are installed. In a system with one video card, enumerating the drivers will reveal only one option, usually named "Primary Display Driver". If you have more than one video card, n + 1 drivers will be enumerated, where n is the number of video cards installed(If you have 2 video cards, 3 drivers will be enumerated). The reason n + 1 Drivers are enumerated is because the Primary Display Driver is always enumerated first. This is the Driver that your start menu will be located on. The Primary Display Driver will then be enumerated a second time, but with the manufacturer's identification made available. Each of the drivers enumerated will be identified by a descriptive name, as well as a GUID(Global Unique Identifier). The GUID is the value you later use when creating the DirectDraw object.
A Display Mode is simply a combination of width, height, depth and other more advanced information. When you enumerate the Display Modes, you will be given a list of Resolutions that you can save for later use.
A Device is a way for Direct 3D to determine how to render primitives. On a Driver that supports Hardware 3D acceleration, you will usually enumerate a minimum of 2 Devices. The first is the Software RGB Rasterizer which DirectX describes as RGB Emulation. The second Device is the Direct3D HAL(Hardware Abstraction Layer), or simply, the 3D accelerated card installed. Each Device also has a GUID that can be saved and later used when Creating a Device.
Each type of enumeration is accomplished by calling an Enumeration function(such as DirectDrawEnumerateEx) and passing the pointer of a callback function. The callback function is called by the Enumeration function once for every object enumerated. For example:
// Callback Declaration
HRESULT CALLBACK EnumDisplayDrivers( GUID FAR* pGuid,
LPSTR Desc,
LPSTR Name,
LPVOID Context,
HMONITOR hMonitor );
// Enumeration Function
DirectDrawEnumerateEx(( LPDDENUMCALLBACKEX )EnumDisplayDrivers,
NULL,
DDENUM_ATTACHEDSECONDARYDEVICES |
DDENUM_DETACHEDSECONDARYDEVICES |
DDENUM_NONDISPLAYDEVICES );
For this article, I have decided to create some structures to hold the various enumeration information and linked the structures together in a singly linked list. The DisplayDriver structures is as follows:
struct D3DDeviceList;
struct DisplayModeList;
struct DisplayDriver
{
DisplayDriver* Next;
GUID Guid;
char* Description;
D3DDevice* D3DDeviceList;
DisplayMode* DisplayModeList;
};
DisplayDriver* DisplayDriverList;
D3Device and DisplayMode are defined in a similar way:
struct D3DDevice
{
D3DDevice* Next;
GUID Guid;
char* Name;
BOOL IsHW;
};
struct DisplayMode
{
DisplayMode* Next;
int Width;
int Height;
int Depth;
};
[size="5"]Enumerating Drivers
The call to enumerate Drivers is DirectDrawEnumerateEx(). The parameters passed are: the pointer to the callback function, the pointer to any variable you wish to pass, and lastly, the Flags. For Example:
// Driver Enumeration Function
DirectDrawEnumerateEx(( LPDDENUMCALLBACKEX )EnumDisplayDrivers, NULL,
DDENUM_ATTACHEDSECONDARYDEVICES |
DDENUM_DETACHEDSECONDARYDEVICES |
DDENUM_NONDISPLAYDEVICES );
HRESULT CALLBACK EnumDisplayDrivers( GUID FAR* pGuid, LPSTR DriverDescription,
LPSTR DriverName, LPVOID Context,
HMONITOR HMonitor )
{
DisplayDriver* NewDD = new DisplayDriver;
if( NewDD )
{
ZeroMemory( NewDD, sizeof( DisplayDriver ));
if( pGuid )
CopyMemory( &( NewDD->Guid ), pGuid, sizeof( GUID ));
NewDD->Description = new char[strlen( DriverDescription ) + 1];
if( NewDD->Description )
{
strcpy( NewDD->Description, DriverDescription );
}
NewDD->DisplayModeList = NULL;
NewDD->D3DDeviceList = NULL;
NewDD->Next = NULL;
}
if( !DisplayDriverList )
DisplayDriverList = NewDD;
else
{
for( DisplayDriver* TheDD = DisplayDriverList; TheDD->Next; TheDD = TheDD->Next )
;
TheDD->Next = NewDD;
}
return DDENUMRET_OK;
}
[size="5"]Enumerating Modes
To be able to enumerate the Display Modes, a valid DirectDraw object must already be created with DirectDrawCreateEx(). As you should know, the DirectDrawCreateEx() function takes the GUID of a Display Driver as it's first argument. If this GUID value is NULL, the primary Display Driver is assumed to be the Driver being created. Since the Display Mode and Devices are dependent on the Display Driver created, this is done for each Display Driver enumerated.
for( DisplayDriver* TheDD = DisplayDriverList; TheDD; TheDD = TheDD->Next )
{
DirectDrawCreateEx( &( TheDD->Guid ), ( void** ) &pDirectDraw, IID_IDirectDraw7, NULL );
pDirectDraw->SetCooperativeLevel( hWnd, DDSCL_NORMAL );
// Enumerate Display Modes and D3D Devices
// Release DirectDraw and Direct3D objects
}
// Display Mode Enumeration Function
pDirectDraw->EnumDisplayModes( 0, NULL, &( TheDD->DisplayModeList ),
( LPDDENUMMODESCALLBACK2 )EnumDisplayModes );
HRESULT CALLBACK EnumDisplayModes( LPDDSURFACEDESC2 pDDSD, LPVOID Context )
{
DisplayMode** DMList = ( DisplayMode** )Context;
DisplayMode* NewDM = new DisplayMode;
if( NewDM )
{
NewDM->Width = pDDSD->dwWidth;
NewDM->Height = pDDSD->dwHeight;
NewDM->Depth = pDDSD->ddpfPixelFormat.dwRGBBitCount;
NewDM->Next = NULL;
}
if( !( *DMList ))
*DMList = NewDM;
else
{
for( DisplayMode* TheDM = *DMList; TheDM->Next; TheDM = TheDM->Next )
;
TheDM->Next = NewDM;
}
return DDENUMRET_OK;
}
[size="5"]Enumerating Devices
To be able to enumerate the Devices, a valid Direct3D object must be available. This is achieved by using QueryInterface on the DirectDraw object as shown below:
// QueryInterface for the Direct3D Object
pDirectDraw->QueryInterface( IID_IDirect3D7, ( void** ) &pDirect3D );
pDirect3D->EnumDevices(( LPD3DENUMDEVICESCALLBACK7 )EnumDevices,
&( TheDD->D3DDeviceList ));
HRESULT CALLBACK EnumDevices( LPSTR DeviceDescription, LPSTR DeviceName,
LPD3DDEVICEDESC7 pD3DDD, LPVOID Context )
{
D3DDevice** DevList = ( D3DDevice** )Context;
D3DDevice* NewDev = new D3DDevice;
if( NewDev )
{
CopyMemory( &( NewDev->Guid ), &( pD3DDD->deviceGUID ), sizeof( GUID ));
NewDev->Name = new char[strlen( DeviceName ) + 1];
if( NewDev->Name )
{
strcpy( NewDev->Name, DeviceName );
}
NewDev->IsHW = pD3DDD->dwDevCaps & D3DDEVCAPS_HWRASTERIZATION;
NewDev->Next = NULL;
}
if( !( *DevList ))
*DevList = NewDev;
else
{
for( D3DDevice* TheDev = *DevList; TheDev->Next; TheDev = TheDev->Next )
;
TheDev->Next = NewDev;
}
return DDENUMRET_OK;
}
[size="5"]Cleaning up
As mentioned above, during enumeration of the various Display Modes and Devices, the DirectDraw and Direct3D objects should be released at the end of the loop.
Releasing all the allocated memory is always a good idea, and can be achieved as follows:
if( DisplayDriverList )
{
DisplayDriver* TheDD, * NextDD;
for( TheDD = DisplayDriverList; TheDD; TheDD = NextDD )
{
NextDD = TheDD->Next;
if( TheDD->Description )
delete TheDD->Description;
if( TheDD->DisplayModeList )
{
DisplayMode* TheDM, * NextDM;
for( TheDM = TheDD->DisplayModeList; TheDM; TheDM = NextDM )
{
NextDM = TheDM->Next;
delete TheDM;
}
}
if( TheDD->D3DDeviceList )
{
D3DDevice* TheDev, * NextDev;
for( TheDev = TheDD->D3DDeviceList; TheDev; TheDev = NextDev )
{
NextDev = TheDev->Next;
if( TheDev->Name )
delete TheDev->Name;
delete TheDev;
}
}
delete TheDD;
}
DisplayDriverList = NULL;
}
[size="5"]Conclusion
As you can see, there is more work involved than what I have outlined here. You need a way to either automatically select the enumerated values, or a way to allow your users to select them. Most of the information contained in this article is also in the DirectX 7 Docs. If you need more information, I suggest looking there.
For a full, uninterrupted listing of the code contained in this article, see the attached file.
- [email="alamar@cgocable.net"]Nathan Davies[/email]
Worlds of Wonder Games
Sept 28, 1999