Archived

This topic is now archived and is closed to further replies.

Biggles

Help with Device Enumeration

Recommended Posts

Does anyone know of any tutorials available that explain finding out which devices are available on a system and what modes and stuff they support? I think that it''s called Device Enumeration but I''m not sure. I''ve tried going through the code in the d3dapp.cpp file and I understand some of it, but there is much that I don''t understand, especially about multiple devices, etc. If there arn''t any tutorials, could someone explain the concept here please? -------------------- Never eat anything bigger than your own head.

Share this post


Link to post
Share on other sites
Sorry to Biggles if you''ve got e-mail notification of a reply and come here hoping for an answer, but I''d just like to say that I''m curious about this too. Most D3D tutorials don''t go into device enumeration too much and I''ve yet to find a decent book that explains it properly.

RM.



Tron Software

-=Kicking Butt and Writing Code=-

Share this post


Link to post
Share on other sites
I must have missed that when I did my search. It''s quite a good article, but some of the function calls and methods for doing things appear to have changed between 7 and 8. Does anyone know of a similar article based on DirectX 8?


--------------------

Never eat anything bigger than your own head.

Share this post


Link to post
Share on other sites
I find that the DirectX SDK help file is very adequate for this. It provides complete descriptions of the necessary functions, structures and expected behaviors. I'll post some code that I wrote just from dealing with it (I ripped it out of my own code and cleaned it up a little so it's easier to understand):

    
// Direct3D8 "Device Enumeration"

static LPDIRECT3D8 lp_D3D = NULL;
static LPDIRECT3DDEVICE8 lp_D3DDevice = NULL;
static unsigned int s_nAdapters;
//...

static vector<string> s_vecDeviceNames;
static vector<string> s_vecDriverNames;
static vector<int> s_vecAdapterModes;
//...

s_nAdapters = lp_D3D->GetAdapterCount();
//...

for(int n = 0; n < s_nAdapters; ++n)
{
D3DADAPTER_IDENTIFIER8 id;
lp_D3D->GetAdapterIdentifier(n, D3DENUM_NO_WHQL_LEVEL, &id);
s_vecDeviceNames.push_back(id.Description);
s_vecDriverNames.push_back(id.Driver);

s_vecAdapterModes.push_back(lp_D3D->GetAdapterModeCount(n));
}
//...

for(int n = 0; n < s_nAdapters; ++n)
{
cout << s_vecDeviceNames[n] << endl;
cout << s_vecDriverNames[n] << endl;
for(int m = 0; m < s_vecAdapterModes[n]; ++m)
{
lp_D3D->EnumAdapterModes(n, m, &d3ddm);
cout << d3ddm.Width << 'x'
<< d3ddm.Height << 'x'
<< d3ddm.Format << '@'
<< d3ddm.RefreshRate << endl;
}
}


  
// DirectInput Device Enumeration

static LPDIRECTINPUT8 lp_DI = NULL;
//...

static vector<string> s_vecDeviceNames;
static vector<DIDEVICEINSTANCE> s_vecDevices;
static vector<DIDEVICEOBJECTINSTANCE> s_vecDeviceObjects;
//...

HRESULT EnumDevices(DWORD dwDevType, DWORD dwFlags)
{
// currently I pass nothing to the callback

// I can pass data through the void *

int nDevice = 0;
return lp_DI->EnumDevices(dwDevType, EnumDevicesCallback,
(void *)&nDevice, dwFlags);
}
//...

BOOL CALLBACK EnumDevicesCallback(LPCDIDEVICEINSTANCE lpddi, void *pvRef)
{
s_vecDeviceNames.push_back(lpddi->tszProductName);
// just to show how the passed data can be used, I'll

// increment the passed integer

int i = (int)*pvRef;
++i;
return DIENUM_CONTINUE;
}


I hope you find that useful.
[Edit:] formatting.

Edited by - Oluseyi on November 18, 2001 1:34:03 PM

Share this post


Link to post
Share on other sites
Thanks, that''s very helpful. I know it sounds stupid, but where in the help files is the stuff about D3D enumeration? I looked around but couldn''t find anything, and when I tried doing a search I found lots for things like DirectPlay and Input, but nothing for D3D.

Share this post


Link to post
Share on other sites
If you think about it, you''re not really enumerating devices under D3D (which is why I wrote "device enumeration" in quotes). You query the interface for the number of available adaptors and then query those for their capabilities.

In the Contents panel:
DirectX 8.1 (I''ve migrated)
->DirectX Graphics
->Programmer''s Guide
->Using Direct3D
->About Devices
->Using Devices
->Determining Hardware Support
->Selecting a Device

Share this post


Link to post
Share on other sites
OK, the next thing I''m confused about is where to do this and where to store the info I get out. If I have a D3DDevice class, should I store the info in there? That makes sense to me since a d3ddevice should know what adapters, devices and modes it has available to it. Once I know where, when should it be done? I would assume it should be done before or while creating the device. And finally, how is the info used to select a good default adapter, and what is the best way to save the information needed to select a customised configuration in a config file so that it will load with the user''s preffered option the next time the game loads?


--------------------

Never eat anything bigger than your own head.

Share this post


Link to post
Share on other sites
In our (DX8) engine we have an array of D3DAdapterInfo structures. One for each graphics card found in the system. A direct cut & paste:

  
typedef struct
{
bool bValid; // is this device valid for Paradox use


UINT uiAdapterOrdinal; // required to create device under DX8

D3DADAPTER_IDENTIFIER8 identifier; // nice name and driver information

D3DCAPS8 caps; // capabilities of this device


long lRating; // how good is device (score can be -ve)


HMONITOR hMonitor; // which monitor is this connected to

UINT uiModeCount; // number of valid modes in the array

UINT uiDefaultMode; // initial default mode

UINT uiCurrentMode; // current mode for this screen

D3DDISPLAYMODE* modes; // list of usable modes for device


DWORD dwShaderVPUsage; // usage flags for shader vertex processing ops

DWORD dwFixedVPUsage; // usage flags for fixed vertex processing ops

DWORD dwCreationFlags; // flags for the creation of this adapter

}
D3DAdapterInfo;



Depending on the application we either automatically choose a device to start the application with (usually based on it''s rating field *). Or we bring up a dialog box where the user can choose which device and mode to use.

The selection which the user makes is best stored into the registry. The best location will be under HKEY_CURRENT_USER, *NOT* HKEY_LOCAL_MACHINE (remember the computer may have multiple users, each may want to play your game at different resolutions, on a different adapter etc. This is also the reason you shouldn''t just save the settings to a file).

A few hints about storing device selections to the registry:

a. Save the selection just before the application is about to exit, DON''T save it just after the selection has been made. If the setting someone chose crashed your game before it started, the settings won''t get saved so the user will be given the choice again (the correct way).

b. Always have a command line switch or some other method to ignore the settings in the registry and go back to the configuration screen. We do this by installing an extra shortcut which has a command line of -config.

c. When storing selections to the registry, also store some sort of check to see which devices are installed and how many there are (we take a checksum (CRC32) of all their device identifiers and store that), if the checksum doesn''t match what you enumerate at startup, you bring up the settings dialog. This way if the user installs a new graphics card, removes a graphics card, updates their drivers etc they get a new settings dialog.


* We work out a rating for each device when enumerating. Essentially we check all the device caps which affect our engine and add a score to the rating for them. Any caps which indicate problematic cards or functionality we have a negative score for (i.e. subtract from the rating). Other factors such as whether the driver is WHQL certified, which modes and texture formats are available etc are also taken into account. We use the rating to decide which adapter is chosed as default. In situations where there''s a tie, we pick the adapter with the lowest ordinal (since it''s likely to be the primary AGP adapter).
In an ideal world we''d also include some sort of runtime profiling of each device in the rating, but in practice the above seems ok in all the situations it''s been tested in).


--
Simon O''''Connor
Creative Asylum Ltd
www.creative-asylum.com

Share this post


Link to post
Share on other sites
I think I may be missing something in the heirarchy here. Correct me if I''m wrong, but is it:

Adapters -> (many) Devices -> (many) Display modes

So for each adapter you find out which devices it has, and for each device which compatible display modes it can run it? Then you let the user choose which adapter to use, which device on that adapter, and which display mode?


--------------------

Never eat anything bigger than your own head.

Share this post


Link to post
Share on other sites
Some more of our engine may help (DX8 specific):

  
typedef struct
{
HMONITOR hMonitor;
UINT uiDefaultAdapter; // initial default adapter for this monitor

UINT uiCurrentAdapter; // current adapter for this monitor

UINT uiAdapterCount; // number of adapters for this monitor

D3DAdapterInfo** pAdapterList; // list of pointers to adapters for this monitor

}
D3DAdapterInfoList;




class D3DAdapterEnum
{
public:
D3DAdapterEnum();
~D3DAdapterEnum();

void SetD3D( LPDIRECT3D8 pD3D );

HRESULT EnumerateHardware();
void ReleaseResources();

HRESULT ChooseDefaults( UINT uiMonitor );

UINT GetMonitorCount() const;
UINT GetAdapterCount( UINT uiMonitor ) const;
GFXADAPTERINFO GetAdapterInfo( UINT uiMonitor, UINT uiAdapter ) const;
GFXMODEINFO GetAdapterMode( UINT uiMonitor, UINT uiAdapter, UINT uiMode ) const ;
HRESULT SetAdapterForMonitor( UINT uiMonitor, UINT uiAdapter );
HRESULT SetModeForAdapter( UINT uiMonitor, UINT uiAdapter, UINT uiMode );

UINT GetCurrentAdapter( UINT uiMonitor ) const;

D3DAdapterInfo* GetD3DAdapterInfo( UINT uiMonitor, UINT uiAdapter ) const;


protected:
bool EnumerateAdapter( UINT uiAdapterOrdinal, D3DAdapterInfo* info );
HRESULT CheckAdapterCaps( UINT uiAdapterOrdinal, D3DAdapterInfo* info );
HRESULT GetAdapterModes( UINT uiAdapterOrdinal, D3DAdapterInfo* info );
HRESULT CreateMonitorList();
HRESULT MakeAdapterListForMonitor( UINT uiMonitor );


protected:
LPDIRECT3D8 m_pD3D;

UINT m_uiMonitorCount;
D3DAdapterInfoList* m_pMonitorList;

UINT m_uiAdapterCount;
D3DAdapterInfo* m_pAdapterList;
};



It goes like this:


Monitor
Adapter
Modes[]

Adapter
Modes[]

Adapter
Modes[]

Monitor
Adapter
Modes[]

Adapter
Modes[]


1. You have physical monitors. In Windows each monitor has a unique HMONITOR.

2. Each monitor can have one or more adapters connected to it. An adapter is a physical graphics card in the system capable of output to the monitor.

3. Each adapter (graphics card) has a set of display modes it can handle

An example: A system with a single monitor, with a S3 ViRGE *AND* a 3Dfx Voodoo2 card connected to that. The system has one monitor with 2 adapters and each adapter has it''s own set of modes. The Voodoo2 can only 16bit modes wheras the ViRGE can do 32bit as well.


Monitor1
S3 ViRGE
Array_of_16,24_and_32bit_modes[]

3Dfx Voodoo2
Array_of_16_bit_modes[]



Each adapter can have a Direct3D HAL device created for it.

The above is generally the way you should think about all adapters in the system.

Cards with multimonitor support (as Matrox DualHead, nVidia TwinView etc) or TVOut will show up in the system as *2* adapters, one for each monitor (or TV) they have connected.
You don''t need to change your approach for those because they appear as 2 separate cards, everything should be automatic.
The only thing to bear in mind is that although it''s the same chip handling both monitors, the display capabilities and output modes may be different for each monitor (a TV usually can''t support all the modes a monitor can for example).

Some more examples:

An AGP G400 DualHead card with 2 monitors connected.

Monitor1
Adapter=G400 display1
Modes[]

Monitor2
Adapter=G400 display2
Modes[]


An AGP G400 DualHead card with 1 monitor connected.

Monitor1
Adapter=G400
Modes[]


An AGP G400 with 2 monitors and a PCI TNT with a 3rd.

Monitor1
Adapter=G400 display1
Modes[]

Monitor2
Adapter=G400 display2
Modes[]

Monitor3
Adapter=TNT
Modes[]


An AGP G400 with 2 monitors + a Voodoo2 connected to the 1st
[It''s unlikely anyone would be that sick though!]

Monitor1
Adapter=G400 display1
Modes[]

Adapter=Voodoo2
Modes[]

Monitor2
Adapter=G400 display2
Modes[]



--
Simon O''''Connor
Creative Asylum Ltd
www.creative-asylum.com

Share this post


Link to post
Share on other sites
Ah thanks! It''s all starting to make a weird kind of sense now!


--------------------

Never eat anything bigger than your own head.

Share this post


Link to post
Share on other sites