DirectX 7 Enumeration
by Nathan Davies from Alamar's Domain
Sept 28, 1999

The Basics of Enumeration and the Structures Involved

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 );

In this example, calling DirectDrawEnumerateEx would invoke DirectX to call EnumDisplayDrivers once for each Driver enumerated.

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;

This structure can be used to save the Driver's GUID and Description. DisplayDriverList is declared as a global variable to access the List for Display Drivers. Since Display Modes and Devices are dependant on the Driver, their Lists are included inside the Driver structure.

D3Device and DisplayMode are defined in a similiar way:


struct D3DDevice
{
   D3DDevice* Next;
	
   GUID Guid;
   char* Name;
   BOOL IsHW;
};

struct DisplayMode
{
   DisplayMode* Next;
	
   int Width;
   int Height;
   int Depth;
};

Enumerating Drivers

The call to enumerate Drivers is DirectDrawEnumerateEx(). The paramters 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 );

EnumDisplayDrivers will be called once for each Driver found. The code for EnumDisplayDrivers follows


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;
}

In EnumDisplayDrivers, a new DisplayDriver variable is allocated. This structure is filled with the relevant information that was passed to this function. I chose to save the GUID and the Description. The name of a Driver is currently not very descriptive and in my opinion, not nearly as useful as the Description. The D3DDevice and DisplayMode lists are initialized to NULL along with the Next value. Next, the Driver is added to the end of the global DisplayDriverList.

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 dependant 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
}

The call to enumerate Display Modes is EnumDisplayModes(). The paramters passed are: the flags, a DDSURFACEDESC2 value to check against, the pointer to any variable you wish to pass, and lastly, the pointer to the callback function For Example:


// Display Mode Enumeration Function
pDirectDraw->EnumDisplayModes( 0, NULL, &( TheDD->DisplayModeList ),
                             ( LPDDENUMMODESCALLBACK2 )EnumDisplayModes );

EnumDisplayModes will be called once for each Display Mode found. Notice that I passed the address of the current DisplayDriver's DisplayModeList variable. The code for EnumDisplayModes follows


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;
}

In EnumDisplayModes, a variable is declared to refer to the DisplayModeList value passed. A new DisplayMode variable is then allocated. This structure is filled with the relevant information that was passed to this function. This information includes the Width, Height and Depth of the Mode Enumerated. Next, the Mode is added to the end of the DisplayModeList passed.

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 );

The call to enumerate Direct3D Devices is EnumDevices(). The parameters passed are: the pointer to any variable you wish to pass, and the pointer to the callback function For Example:


pDirect3D->EnumDevices(( LPD3DENUMDEVICESCALLBACK7 )EnumDevices,
                         &( TheDD->D3DDeviceList ));

EnumDevices will be called once for each Device found. Notice that I passed the address of the current DisplayDriver's D3DDeviceList variable. The code for EnumDevices follows


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;
}

In EnumDevices, a variable is declared to refer to the D3DDeviceList value passed. A new D3DDevice variable is then allocated. This structure is filled with the relevant information that was passed to this function. As opposed to the Display Driver information, the Name is more useful for our purposes. The GUID is saved, as well as a BOOL flag to indicate whether the Device is Hardware or Software. Next, the Device is added to the end of the D3DDeviceList passed.

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;
}

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, click here

- Nathan Davies
Worlds of Wonder Games

Discuss this article in the forums


Date this article was posted to GameDev.net: 10/11/1999
(Note that this date does not necessarily correspond to the date the article was written)

See Also:
General

© 1999-2011 Gamedev.net. All rights reserved. Terms of Use Privacy Policy
Comments? Questions? Feedback? Click here!