Enumerating Devices: The RMEnum Sample

Device enumeration is one of the fundamental concepts in Microsoft® Direct3D® Retained Mode. You can create a default device without device enumeration, but enumerating devices provides more flexibility for your application. It enables you to identify the devices present on a given system, and to choose the device that is most appropriate for your needs and can offer more advanced capabilities.

This article walks you through code from the RMEnum sample. RMEnum enumerates the hardware and software devices on the system, makes an intelligent choice about which device to use, and creates the device. It adds the enumerated devices to the application's File menu and enables the user to switch between devices by choosing devices from the menu. Unlike a real-world Direct3D Retained Mode application, RMEnum does not display any three-dimensional (3-D) objects or render any graphics. It focuses on enumeration for the sake of simplicity and clarity.

This article assumes you are an experienced C or C++ programmer and are familiar with beginning Windows programming concepts, such as the message loop, creating a window, and using menus.

This article contains the following sections.

Dependencies

RMEnum links to DDraw.lib and D3DRM.lib. It uses the Msg and D3DRMErrorToString helper functions from Error.c to provide detailed information in case of errors. By default, Error.c resides in the Samples\Misc directory of the Microsoft® DirectX® Software Development Kit (SDK). RMEnum could just as easily use the standard Microsoft® Windows® MessageBox function instead of using these helper functions, but the error reporting would not be as clean or detailed. The sample also links to the menus and other resources provided by RMEnum.rc.

To properly initialize globally unique identifiers (GUIDs), Direct3D applications must contain the following definition at the top of their code.

#define INITGUID

The sample's RMEnum.cpp file includes the following header files.

#include <windows.h>   /* Standard Windows header file */
#include <direct.h>    /* DirectDraw definitions */
#include <d3drmwin.h>  /* D3DRM definitions */
#include "RMEnum.h"    /* Defines constants used in RMEnum.rc */
#include "rmerror.h"   /* Prototypes for error reporting functions in
                             rmerror.c */

Global Declarations

The sample defines some constants and global variables to help organize information.

For simplicity, RMEnum.cpp sets an arbitrary limit on the number of drivers it will enumerate. You could enumerate drivers once to count how many are available, and enumerate again to examine each driver's capabilities as this sample does. A real-world same could also maintain a linked list of driver information rather than a fixed size array as RMEnum does.

#define MAX_DRIVERS 5  /* Arbitrary limit for the maximum number of  
                            Direct3D drivers expected to be found */

The application-defined RELEASE macro releases an object only if the object is not NULL and ensures that an object is initialized to NULL after it has been released. This practice eliminates the problem of trying to release an object that has already been released, which can, in turn, cause undesired results, such as page faults.

/* Macro to release an object. */
#define RELEASE(x) if (x != NULL) {x->Release(); x = NULL;} 

The myglobs structure collects device and driver information, as well as information about the current application state. The Direct3D Retained Mode and DirectDrawClipper objects are also global (the DirectDrawClipper object restricts drawing to a designated area).

/* 
 * GLOBAL VARIABLES
 */
LPDIRECT3DRM lpD3DRM = NULL;            /* Direct3DRM object */
LPDIRECTDRAWCLIPPER lpDDClipper = NULL; /* DirectDrawClipper object */

struct _myglobs {
    /* Direct3DRM device */
    LPDIRECT3DRMDEVICE dev;           

    /* GUIDs of the available Direct3D (D3D) drivers */
    GUID DriverGUID[MAX_DRIVERS];     

    /* names of the available D3D drivers */
    char DriverName[MAX_DRIVERS][50]; 

    /* counter indicating the number of available D3D drivers */
    int  NumDrivers;                  

    /* number of the D3D driver currently being used; set to
     * the preferred driver by enumDeviceFunc */
    int  CurrDriver;                  

    /* program is about to terminate */
    BOOL bQuit;                 

    /* all D3DRM objects have been initialized */
    BOOL bInitialized;          
    
    /* bit depth of the current display mode */
    DWORD BPP;                  
} myglobs;

Function Summaries

RMEnum.cpp contains the following functions.

CreateObjects is the master function that initializes the global variables, uses EnumDevices to enumerate the devices, and creates objects. InitApp uses CreateObjects.

BOOL CreateObjects(HWND win);

GetCurrentBitDepth returns the current bit depth to CreateObjects.

static DWORD GetCurrentBitDepth(HWND);

EnumDevices obtains the Direct3D object to use for device enumeration, uses the Immediate Mode IDirect3D::EnumDevices function, passing the enumDeviceFunc callback function as a parameter, and places the enumerated devices on the File menu.

static BOOL EnumDevices(HWND);

The enumDeviceFunc callback function cycles through the available drivers, chooses the usable drivers, and selects a preferred driver.

static HRESULT WINAPI enumDeviceFunc(LPGUID,
                                            LPSTR,
                                            LPSTR,
                                            LPD3DDEVICEDESC, 
                                            LPD3DDEVICEDESC,
                                            LPVOID);

RMEnum uses ChangeDriver in response to the user's menu choice that switches to a differenct available driver.

void ChangeDriver(HWND, WPARAM);

CleanUpAndQuit releases objects in preparation for application termination.

static void CleanUpAndQuit(void);

The remaining functions (InitApp, WinMain, AppAbout, and WindowProc) are primarily based on the standard Windows functions and contain little code specific to the RMEnum sample.

static HWND InitApp(HINSTANCE, int);
int APIENTRY WinMain (HINSTANCE, HINSTANCE, LPSTR, int);
LRESULT CALLBACK AppAbout(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK WindowProc(HWND, UINT, WPARAM, LPARAM);

For more information about these functions, see the following topics in this article.

CreateObjects

CreateObjects provides and uses the core code in the RMEnum sample. After initializing global variables and obtaining the current bit depth, it uses EnumDevices to actually enumerate the devices. The enumDeviceFunc callback function later uses the bit depth to determine which drivers support the current bit depth.

After enumerating devices, CreateObjects performs the steps necessary to create a device and associate it with the application's main window as follows:

  1. Uses Direct3DRMCreate to create a Direct3D Retained Mode object.
  2. Uses the DirectDraw DirectDrawCreateClipper function to create a DirectDrawClipper object.
  3. Uses the DirectDrawClipper's SetHWnd method to associate the application's window with the clipper. The DirectDrawClipper object restricts subsequent drawing to a designated area—in this case, to the application window.
  4. Uses the Windows GetClientRect function to obtain the height and width of the application's main window.
  5. Uses the Direct3D Retained Mode object's CreateDeviceFromClipper method to create the device, passing the preferred driver selected by the enumeration, and the width and height of the application's window, to the method.

The CreateObjects function's source code follows:

/*
 * Initialize globals, enumerate devices and create objects. 
 */
BOOL CreateObjects(HWND win)
{
    HRESULT rval; /* Return value */
    RECT rc;      /* Bounding rectangle for main window */

    /*
     * Initialize the global variables. 
     */
    memset(&myglobs, 0, sizeof(myglobs));

    /*
     * Record the current display bit depth.
     */
     myglobs.BPP = GetCurrentBitDepth(win);

    /*
     * Enumerate the D3D drivers and select one.
     */
    if (!EnumDevices(win))
        return FALSE;

    /*
     * Create the Direct3DRM object and the window object.
     */
    rval = Direct3DRMCreate(&lpD3DRM);
    if (rval != D3DRM_OK) {
        Msg("Failed to create Direct3DRM.\n%s", D3DRMErrorToString(rval));
        return FALSE;
    }

    /*
     * Create a DirectDrawClipper object and associate the window with it.
     */
    rval = DirectDrawCreateClipper(0, &lpDDClipper, NULL);
    if (rval != DD_OK) {
        Msg("Failed to create DirectDrawClipper object");
        return FALSE;
    }
    rval = lpDDClipper->SetHWnd(0, win);
    if (rval != DD_OK) {
        Msg("Failed to set the window handle for the DirectDrawClipper");
        return FALSE;
    }
    /*
     * Create the D3DRM device with the selected D3D driver. The GUID can 
     * be NULL to create a default device without enumeration.
     */
    GetClientRect(win, &rc);

    rval = lpD3DRM->CreateDeviceFromClipper(lpDDClipper, 
                           &myglobs.DriverGUID[myglobs.CurrDriver],
                           rc.right, rc.bottom, &myglobs.dev);

    if (rval) {
        Msg("Failed to create the D3DRM device.\n%s", 
            D3DRMErrorToString(rval));
        return FALSE;
    }

    /*
     * Globals are initialized
     */
    myglobs.bInitialized = TRUE;

    return TRUE;
}

GetCurrentBitDepth

The GetCurrentBitDepth function determines the current device's bit depth capabilities and returns that information in the form of a DirectDraw bit depth flag. Bit depth refers to the device's color settings, such as 8-bit, 16-bit, or 24-bit color.

/*
 * Retrieve a DirectDraw bit depth flag reflecting the current 
 * display bit depth in bits per pixel (BPP).
 */
static DWORD GetCurrentBitDepth(HWND win) 
{
    HDC hdc;
    int BPP;

    /*
     * Record the current display's BPP.
     */
    hdc = GetDC(win);
    BPP = GetDeviceCaps(hdc, BITSPIXEL);
    ReleaseDC(win, hdc);

    /*
     * Map BPP value to DirectDraw bit depth.
     */
    switch(BPP) {
        case 1:
            return DDBD_1;
        case 2:
            return DDBD_2;
        case 4:
            return DDBD_4;
        case 8:
            return DDBD_8;
        case 16:
            return DDBD_16;
        case 24:
            return DDBD_24;
        case 32:
            return DDBD_32;
        default:
            return 0;
    }
}

EnumDevices

The RMEnum EnumDevices function performs the following steps to enumerate devices. The steps include variables and code fragments from the sample.

  1. Uses the DirectDrawCreate function to create a DirectDraw object.
    LPDIRECTDRAW lpDD;
    HRESULT rval;
    rval = DirectDrawCreate(NULL, &lpDD, NULL);
    
  2. Uses the DirectDraw object's QueryInterface method to obtain a Direct3D Immediate Mode object. For more information on Direct3D Immediate Mode, see the DirectX Foundation SDK.
    LPDIRECT3D lpD3D;
    rval = lpDD->QueryInterface(IID_IDirect3D, (void**) &lpD3D);
    
  3. Uses the Direct3D Immediate Mode object's IDirect3D::EnumDevices function to enumerate the devices on the system. IDirect3D::EnumDevices passes the enumDeviceFunc callback function as a parameter.
    rval = lpD3D->EnumDevices(enumDeviceFunc, &myglobs.CurrDriver);
    

    The callback function cycles through the available devices and selects the drivers that are compatible with certain criteria.

After it has enumerated the drivers, RMEnum's EnumDevices function releases the DirectDraw and Direct3D devices and adds the drivers to the application's File menu.

lpD3D->Release();
lpDD->Release();

Complete source code for RMEnum's EnumDevices function follows:

/*
 * EnumDevices
 * Enumerate the available Direct3D drivers, add them to the file menu, 
 * and choose one to use.
 */
static BOOL EnumDevices(HWND win)
{
    LPDIRECTDRAW lpDD;
    LPDIRECT3D lpD3D;
    HRESULT rval;
    HMENU hmenu;
    int i;

    /*
     * Create a DirectDraw object and query for the Direct3D interface 
     * to use to enumerate the drivers.
     */
    rval = DirectDrawCreate(NULL, &lpDD, NULL);
    if (rval != DD_OK) {
        Msg("Creation of DirectDraw HEL failed.\n%s", 
            D3DRMErrorToString(rval));
        return FALSE;
    }
    rval = lpDD->QueryInterface(IID_IDirect3D, (void**) &lpD3D);
    if (rval != DD_OK) {
        Msg("Creation of Direct3D interface failed.\n%s", 
            D3DRMErrorToString(rval));
        lpDD->Release();
        return FALSE;
    }
    /*
     * Enumerate the drivers. Pass the arbitrary value of -1 as
     * application-defined data. The enumDeviceFunc callback function
     * performs special-case initialization code in response to a -1.
     */
    myglobs.CurrDriver = -1;
    rval = lpD3D->EnumDevices(enumDeviceFunc, &myglobs.CurrDriver);
    if (rval != DD_OK) {
        Msg("Enumeration of drivers failed.\n%s", 
            D3DRMErrorToString(rval));
        return FALSE;
    }
    /*
     * Make sure we found at least one valid driver.
     */
    if (myglobs.NumDrivers == 0) {
        Msg("Could not find a D3D driver that is compatible with this \
            display depth");
        return FALSE;
    }
    lpD3D->Release();
    lpDD->Release();
    /*
     * Add the driver names to the File menu.
     */
    hmenu = GetSubMenu(GetMenu(win), 0);
    for (i = 0; i < myglobs.NumDrivers; i++) {
        InsertMenu(hmenu, 2 + i, MF_BYPOSITION | MF_STRING, 
                   MENU_FIRST_DRIVER + i,
                   myglobs.DriverName[i]);
    }
    return TRUE;
}

enumDeviceFunc

Direct3D Immediate Mode defines D3DENUMDEVICESCALLBACK as the prototype definition for the callback function to enumerate installed Direct3D devices. RMEnum provides enumDeviceFunc as this enumeration callback function. Direct3D Immediate Mode calls enumDeviceFunc repeatedly, once for each device on the system, and passes device information to enumDeviceFunc each time. As D3D Immediate Mode loops through the available drivers, enumDeviceFunc saves information about drivers that can render in the current display bit depth into global variables. enumDeviceFunc chooses hardware drivers over software emulation drivers because hardware drivers typically render faster and chooses color lights over monochromatic lights for better-looking 3-D lighting. These choices do not actually come into play with this sample, because RMEnum does no rendering, but they do effect real applications and can have a large impact on the appearance of 3-D graphics.

enumDeviceFunc's parameters include the device's globally unique identifier (GUID), a textual description of the device, and the device name. Other parameters include pointers to two Direct3D Immediate Mode D3DDEVICEDESC structures. The first structure (lpHWDesc) contains the hardware capabilities of the Direct3D device and the other structure (lpHELDesc) contains the Hardware Emulation Layer (HEL) software emulation capabilities of the device. The last parameter is application-defined data passed to this callback function the first time Direct3D Immediate Mode calls it.

Complete source code for RMEnum's enumDeviceFunc callback function follows:

/*
 * enumDeviceFunc
 * Callback function which records each usable D3D driver's name and 
 * GUID. It selects the preferred driver on the system by choosing
 * hardware drivers over software drivers, and color lights over 
 * monochrome lights. It sets myglobs.CurrDriver to indicate the 
 * preferred driver.
 */
static HRESULT WINAPI enumDeviceFunc(LPGUID lpGuid, 
                                     LPSTR lpDeviceDescription, 
                                     LPSTR lpDeviceName,
                                     LPD3DDEVICEDESC lpHWDesc, 
                                     LPD3DDEVICEDESC lpHELDesc, 
                                     LPVOID lpContext)
{
    static BOOL hardware = FALSE; /* current start driver is software */
    static BOOL mono = FALSE;     /* current start driver is color */
    LPD3DDEVICEDESC lpDesc;       /* description of current driver */
    
    /* 
     * Data defined by this application that, when it equals -1, 
     * indicates that the current driver is the first to be enumerated 
     */
    int *lpStartDriver = (int *)lpContext; 

    /*
     * Decide which device description to consult. The driver is either
     * hardware (HW) or software. Software drivers provide support
     * through the hardware emulation layer (HEL).
     */
    lpDesc = lpHWDesc->dcmColorModel ? lpHWDesc : lpHELDesc;
    /*
     * If this driver cannot render in the current display bit depth,
     * return D3DENUMRET_OK to skip this driver. Enumeration continues
     * automatically as D3D calls enumDeviceFunc again for the next
     * driver.
     */
    if (!(lpDesc->dwDeviceRenderBitDepth & myglobs.BPP)) 
        return D3DENUMRET_OK;
    /*
     * Record this driver's GUID and name.
     */
    memcpy(&myglobs.DriverGUID[myglobs.NumDrivers], lpGuid, sizeof(GUID));
    lstrcpy(&myglobs.DriverName[myglobs.NumDrivers][0], lpDeviceName);
    /*
     * Choose hardware over software, and color lights over monochrome
     * lights.
     */
    if (*lpStartDriver == (int)-1) {
        /*
         * This is the first valid driver, so record whether this driver
         * is a hardware driver or not, and whether it is limited to 
         * monochrome lights or not.
         */
        myglobs.CurrDriver = myglobs.NumDrivers;
        hardware = lpDesc == lpHWDesc ? TRUE : FALSE;
        mono = lpDesc->dcmColorModel & D3DCOLOR_MONO ? TRUE : FALSE;
    } else if (lpDesc == lpHWDesc && !hardware) {
        /*
         * If this driver is a hardware driver and the start driver is not,
         * then make this driver the new preferred driver and record
         * its hardware and mono capabilities. The next time D3D calls 
         * enumDeviceFunc, that driver will be compared against this 
         * new start driver.
         */
        myglobs.CurrDriver = 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_RGB && mono) {
            /*
             * If this driver and the start driver are the same type 
             * (both hardware or both software) and this driver is 
             * color while the start driver is not, then make this driver
             * the new preferred driver and record its capabilities.
             * The next time D3D calls enumDeviceFunc, that driver will 
             * be compared against this new start driver.
             */
            myglobs.CurrDriver = myglobs.NumDrivers;
            hardware = lpDesc == lpHWDesc ? TRUE : FALSE;
            mono = lpDesc->dcmColorModel & D3DCOLOR_MONO ? TRUE : FALSE;
        }
    }
    /* 
     * Increment the current driver number in preparation for the next
     * driver. 
     */
    myglobs.NumDrivers++; 

    /* Maximum number of drivers reached, stop enumeration. */
    if (myglobs.NumDrivers == MAX_DRIVERS)
        return (D3DENUMRET_CANCEL);
    /* 
     * Continue enumerating drivers. D3D will call enumDeviceFunc
     * again with information for the next driver. 
     */
    return (D3DENUMRET_OK);
}

ChangeDriver

ChangeDriver is not a required function in the RMEnum sample, but it does add flexibility by enabling the user to choose between drivers. ChangeDriver changes the current driver in response to a menu selection. It re-creates the device by using the Direct3D Retained Mode object's CreateDeviceFromClipper method, much as RMEnum's CreateObjects function originally created the device.

The ChangeDriver source code follows:

/*
 * Release the current device and create the new one.
 */
void ChangeDriver(HWND win, WPARAM wparam)
{
    HRESULT rval;
    RECT rc;

    /* 
     * Save the previous driver selection as LastDriver.
     */
    int LastDriver = myglobs.CurrDriver;

    /*
     * Globals are no longer correct since the selection is changing.
     */
    myglobs.bInitialized = FALSE;

    /* 
     * Release the current device.
     */ 
    RELEASE(myglobs.dev);   

    /* 
     * Set the current driver to the driver selected through the menu.
     */
    myglobs.CurrDriver = LOWORD(wparam)-MENU_FIRST_DRIVER;

    /* 
     * Obtain the window rectangle and create the new current driver
     * for that window using the global DirectDrawClipper object.
     */
    GetClientRect(win, &rc);

    rval = lpD3DRM->CreateDeviceFromClipper(lpDDClipper, 
                             &myglobs.DriverGUID[myglobs.CurrDriver],
                             rc.right, rc.bottom, &myglobs.dev);

    /* 
     * If an error occurred, try to recreate the previous driver.
     */
    if (rval) {
        myglobs.CurrDriver = LastDriver;

        rval = lpD3DRM->CreateDeviceFromClipper(lpDDClipper, 
                                 &myglobs.DriverGUID[myglobs.CurrDriver],
                                 rc.right, rc.bottom, &myglobs.dev);
        if (rval) {
            Msg("Failed to create the D3DRM device.\n%s", 
                D3DRMErrorToString(rval));
            CleanUpAndQuit();
        }
        else {
            Msg("There was not enough video memory available to use the \
            	 3D accelerated hardware device.\nRestoring old software \
            	 device.");
            myglobs.bInitialized = TRUE;
        }
    }
    else {
        /* 
         * Globals are properly initialized again.
         */
        myglobs.bInitialized = TRUE;
    }
}

CleanUpAndQuit

The small CleanUpAndQuit function releases global objects and sets the quit flag to TRUE to indicate that the application is ready to quit.

void CleanUpAndQuit(void)
{
    myglobs.bInitialized = FALSE;
    RELEASE(myglobs.dev);
    RELEASE(lpD3DRM);
    RELEASE(lpDDClipper);
    myglobs.bQuit = TRUE;
}

Standard Windows Functions

The remaining functions in RMEnum.cpp (InitApp, WinMain, AppAbout, and WindowProc) are primarily the standard Windows functions and contain little code that is specific to the RMEnum sample. See the RMEnum sample code for more information.

Related Topics

Now that you are familiar with how to enumerate devices, the code in the Direct3D Retained Mode samples should be easier to understand. Nearly all of these samples use the helper code provided by RMMain to enumerate devices. As a next step, you can look at the Egg or the Globe sample to see how they perform rendering, knowing that they use device enumeration behind the scenes.

For brief summaries of all of the Retained Mode samples, see Samples.


Top of Page Top of Page
© 2000 Microsoft and/or its suppliers. All rights reserved. Terms of Use.