Enumerating Direct3D Device Drivers

Rmmain.cpp calls the locally defined InitApp function as its first action inside the WinMain function, and it fails if InitApp is not successful. InitApp creates the window and initializes all objects that are required to begin rendering a 3D scene.

After performing some standard tasks in the initialization of a Windows application and initializing global variables to acceptable default settings, InitApp verifies that Direct3D device drivers are present by calling the locally defined EnumDrivers function.

EnumDrivers sets up a callback function that examines each of the drivers in the system and picks the best one for the application's needs. This is not the easiest or even the best way to find Direct3D device drivers, however. Instead of setting up an enumeration routine, most applications will allow the system to automatically enumerate the current drivers by calling the IDirect3DRM::CreateDeviceFromClipper method and specifying NULL for the lpGUID parameter. This is the recommended way to create a Retained-Mode device because it always works, even if the user installs new hardware. When you use this method and both a hardware and a software device meet the default requirements, the system always retrieves the hardware device. An application should enumerate devices instead of specifying NULL for lpGUID only if it has unusual requirements.

EnumDrivers first calls the DirectDrawCreate function to create a DirectDraw object and then calls IDirectDraw::QueryInterface to retrieve an interface to Direct3D. The first parameter of this method is the interface identifier (IID) that identifies the IDirect3D COM interface. These identifiers are defined in the D3d.h header file. Each identifier is in the form IID_InterfaceName. For example, the IID for the IDirect3DRMTexture interface is IID_IDirect3DRMTexture.

After creating the Direct3D interface, EnumDrivers calls the IDirect3D::EnumDevices method to enumerate the Direct3D drivers. The first parameter to this method is a D3DENUMDEVICESCALLBACK callback function, in this case named enumDeviceFunc. The second parameter is a pointer to the myglobs structure, which contains the global variables Rmmain.cpp uses to set up the 3D environment. The CurrDriver member of the myglobs structure is simply an integer indicating the number of the Direct3D driver currently being used. The application initializes this value to -1 before calling IDirect3D::EnumDevices; the callback function checks this value to find out whether the driver being enumerated is the first valid driver.

static BOOL EnumDrivers(HWND win)

{

LPDIRECTDRAW lpDD;

LPDIRECT3D lpD3D;

HRESULT rval;

HMENU hmenu;

rval = DirectDrawCreate(NULL, &lpDD, NULL);

rval = lpDD->QueryInterface(IID_IDirect3D, (void**) &lpD3D);

myglobs.CurrDriver = -1;

rval = lpD3D->EnumDevices(enumDeviceFunc, &myglobs.CurrDriver);

lpD3D->Release();

lpDD->Release();

// Code omitted here that adds the driver names to the File menu.

return TRUE;

}

The enumDeviceFunc (D3DENUMDEVICESCALLBACK) callback function records each usable Direct3D device driver's name and globally unique identifier (GUID) and chooses a driver. The following code sample shows the enumDeviceFunc callback function. First, this callback function creates a D3DDEVICEDESC structure and copies the input lpContext parameter to a parameter called lpStartDriver. (The lpContext parameter is application-defined data—it can be any data the application requires.) The fourth parameter passed to enumDeviceFunc is a pointer to a D3DDEVICEDESC structure describing the hardware; the function uses the dcmColorModel member of this structure to determine whether it should examine the description of the hardware or the description of the hardware emulation layer (HEL). Any driver that cannot support the current bit-depth is skipped. The current driver's GUID and name are copied into the myglobs structure. Then the enumDeviceFunc callback function performs a few tests to compare the driver being enumerated against the driver stored as lpStartDriver. Whenever the driver being enumerated is implemented in hardware or uses the RGB instead of the monochromatic color model, that driver becomes the new lpStartDriver.

Finally, enumDeviceFunc increments the variable that counts the number of drivers. If this count exceeds the maximum, it returns D3DENUMRET_CANCEL to cancel the enumeration. Otherwise, it returns D3DENUMRET_OK to continue the enumeration.

static HRESULT WINAPI enumDeviceFunc(

LPGUID lpGuid, LPSTR lpDeviceDescription, LPSTR lpDeviceName,

LPD3DDEVICEDESC lpHWDesc, LPD3DDEVICEDESC lpHELDesc,

LPVOID lpContext)

{

static BOOL hardware = FALSE; // Current start driver is hardware

static BOOL mono = FALSE; // Current start driver is mono light

LPD3DDEVICEDESC lpDesc;

int *lpStartDriver = (int *)lpContext;

// Decide which device description we should consult.

lpDesc = lpHWDesc->dcmColorModel ? lpHWDesc : lpHELDesc;

// If this driver cannot render in the current display bit depth, skip

// it and continue with the enumeration.

if (!(lpDesc->dwDeviceRenderBitDepth & BPPToDDBD(myglobs.BPP)))

return D3DENUMRET_OK;

// Record this driver's info.

memcpy(&myglobs.DriverGUID[myglobs.NumDrivers], lpGuid, sizeof(GUID));

strcpy(&myglobs.DriverName[myglobs.NumDrivers][0], lpDeviceName);

// Choose hardware over software, RGB lights over mono lights.

if (*lpStartDriver == -1) {

// This is the first valid driver.

*lpStartDriver = myglobs.NumDrivers;

hardware = lpDesc == lpHWDesc ? TRUE : FALSE;

mono = lpDesc->dcmColorModel & D3DCOLOR_MONO ? TRUE : FALSE;

} else if (lpDesc == lpHWDesc && !hardware) {

// This driver is hardware and start driver is not.

*lpStartDriver = myglobs.NumDrivers;

hardware = lpDesc == lpHWDesc ? TRUE : FALSE;

mono = lpDesc->dcmColorModel & D3DCOLOR_MONO ? TRUE : FALSE;

} else if ((lpDesc == lpHWDesc && hardware ) || (lpDesc == lpHELDesc

&& !hardware)) {

if (lpDesc->dcmColorModel == D3DCOLOR_MONO && !mono) {

// This driver and start driver are the same type, and this

// driver is mono while start driver is not.

*lpStartDriver = myglobs.NumDrivers;

hardware = lpDesc == lpHWDesc ? TRUE : FALSE;

mono = lpDesc->dcmColorModel & D3DCOLOR_MONO ? TRUE : FALSE;

}

}

myglobs.NumDrivers++;

if (myglobs.NumDrivers == MAX_DRIVERS)

return (D3DENUMRET_CANCEL);

return (D3DENUMRET_OK);

}

When EnumDrivers function returns, it calls the IDirect3D::Release and IDirectDraw::Release methods to decrease the reference count of these COM objects. Every time an application calls the Release method on an object, the reference count for that object is reduced by 1. The system deallocates the object when its reference count reaches 0.

Whenever Rmmain.cpp calls a COM method, the return value is checked and the function fails if the method does not succeed. This error-checking has been omitted from the samples in this section for the sake of brevity. Sometimes the error-checking code calls the D3DRMErrorToString function (which is implemented in Rmerror.c in the same directory as Rmmain.cpp). This function simply maps an HRESULT error value to an explanatory string and returns the string.

If any COM interfaces have already been successfully created when a method fails, the error-checking code calls the Release method for that interface before returning FALSE.