Eine Grafikkartenauswahl mit DirectX9 erstellen
© 2003 Manuel Then

Willkommen bei meinem ersten Tutorial. Ziel ist einen Dialog erstellen, in dem eine Grafikkarte, eine dazugehörige Auflösung und Farbtiefe ausgewählt werden können.
Ich setze ein initialisiertes Direct3D9 Objekt (lpD3D) und ein paar WinAPI Kenntnisse voraus.

Zuerst müsst ihr euch mit dem Visual C++ Compiler den Dialog als Ressource erstellen. Ganz einfach im Menü -> Einfügen -> Ressource es erscheint ein Fenster in dem wir Dialog auswählen und mit NEU bestätigen. Das sieht dann (bei WinXP) ca. so aus:

An diesem Dialog könnt ihr dann basteln, bis er euren Ansprüchen entspricht. Wichtig ist nur, dass die Controls die folgend aufgeführten IDs haben.

Text (Static) -> IDC_STATIC
Grafikkartenauswahl (Combo Box) -> IDC_SETGC
Auflösung (Combo Box) -> IDC_SETMODE
Farbtiefe und Z-Buffer (Kontrollkästchen) -> IDC_CHECK1 - IDC_CHECK4
Start (Button) -> IDC_START

Der Dialog sieht ca. dann so aus:

Bevor ihr den Dialog schließt, vergewissert euch, dass die Ressourcendatei gespeichert ist. Wenn ihr schon vorher Ressourcen im Projekt hattet sollte die Datei schon als Script.rc (oder unter irgendeinem anderen Namen) gespeichert worden sein. Andernfalls Datei -> Speichern unter -> Name eingeben (z.B. Script.rc) -> Speichern.

Damit der Compiler die Ressourcen auch erkennt, müssen sie noch zum Projekt hinzugefügt werden. Wieder gilt, wer das Ressource File schon zum Projekt hinzugefügt hat muss den folgenden Schritt auslassen. Links in der IDE (im Arbeitsbereich) nach Dateien wechseln -> Rechtsklick auf einen Ordner (z.B. Ressourcendateien) -> Datei zu Ordner hinzufügen -> Eure Ressourcendatei auswählen -> OK. Nun müsste links von Dateien (auf den TAB Control unterm Arbeitsbereich) Ressourcen erschienen sein, wenn nicht wiederholt einfach die vorhergegangenen Schritte. Den Dialog könnt ihr gegebenenfalls auch noch umbenennen z.B. nach IDD_SELECT oder ähnlich.

Wenn alle Vorbereitungen getroffen sind, können wir auch mit dem Code beginnen.

Da später ein Grafikkarten- / Auflösungarray erstellt wird, müssen wir noch zwei Konstanten mit der maximalen Grafikkartenzahl und der maximalen Zahl der Auflösungen erstellen, die auch an den Anfang der Datei kommen:

#define MAX_ADAPTERS 20
#define MAX_MODES 100

Zur Aufnahme der Grafikkarteninformationen benutzt man am besten eine Struktur ähnlich der Folgenden, die man am besten gleich am Anfang der Quellcodedatei schreibt:

typedef struct ADAPTERINFO_TYPE
{
D3DADAPTER_IDENTIFIER9 adapter;
//GC Infos
UINT display_mode_count;
//Zahl der Displaymodes
D3DDISPLAYMODE modes[MAX_MODES];
//Array der DisplayModes
} ADAPTERINFO;

Um die Informationen am Ende ins Hauptprogramm zu übertragen benötigen wir noch eine Struktur, die aber einfacher ausfällt und kleiner ist, als die Grafikkartenauswahl-interne.

typedef struct ADAPTERSELECT_TYPE
{
UINT adapter;
//interne DirectX ID für die Grafikkarte
int screen_width;
//Auflösung: Breite
int screen_height;
//Auflösung: Höhe
int screen_bpp;
//Farbtiefe
int screen_z_bits;
//Z-Buffer Größe
} ADAPTERSELECT;

Für den Dialog muss eine Callback Funktion ähnlich zur Fenster Callback Funktion geschrieben werden. Sie hat folgende Syntax: und muss direkt nach den Strukturdefinitionen am Anfang der Datei stehen, damit der Compiler sie richtig erkennt:


BOOL CALLBACK SelectProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam);

Jetzt gehts richtig los, wir kommen zur eben als Prototy implementierten Dialog-Callback Funktion. Im Kopf der Funktion steht eine statische Variable, in der Daten über die Grafikkarten temporär gespeichert sind.

BOOL CALLBACK SelectProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
static ADAPTERINFO info[MAX_ADAPTERS];

Nach ihr folgt ein langer Abschnitt, indem der Dialog und die Daten über die Grafikkarten initialisiert werden:

switch(msg)
{
case WM_INITDIALOG:
UINT cur_adapter;

for(cur_adapter=0;cur_adapter<lpD3D->GetAdapterCount();cur_adapter++)
{
if(FAILED(lpD3D->GetAdapterIdentifier(cur_adapter,0,&(info[cur_adapter].adapter))))
{
MessageBox(0,"Kann Adapter-Identifier nicht erfragen",0,0);
return 0;
}
//Adapter der AdapterListe im Dialog hinzufügen
SendDlgItemMessage(hwndDlg,IDC_SETGC,CB_ADDSTRING,0,(LPARAM)info[cur_adapter].adapter.Description);

//Zahl der Bildschirmmodi holen
info[cur_adapter].display_mode_count =lpD3D->GetAdapterModeCount(cur_adapter,D3DFMT_X8R8G8B8);
info[cur_adapter].display_mode_count+=lpD3D->GetAdapterModeCount(cur_adapter,D3DFMT_X1R5G5B5);
info[cur_adapter].display_mode_count+=lpD3D->GetAdapterModeCount(cur_adapter,D3DFMT_R5G6B5);

D3DDISPLAYMODE temp_mode;
UINT temp_pos =0;
bool vorhanden;

//---------------------------------------------
//32 bit Werte
//---------------------------------------------

for(UINT cur_mode=0;cur_mode<info[cur_adapter].display_mode_count;cur_mode++)
{
lpD3D->EnumAdapterModes(cur_adapter,
D3DFMT_X8R8G8B8,
cur_mode,
&temp_mode);

//Nicht mehr zeitgemäß -> aussortieren
if(temp_mode.Width<640 || temp_mode.Height<480)
{
continue;
}

//Zu große Auflösung -> aussortieren
if(temp_mode.Width>1600 || temp_mode.Height>1200)
{
continue;
}


vorhanden=FALSE;
for(UINT temp=0;temp<temp_pos+1;temp++)
{
//Wenn der Mode schon vorhanden ist -> aussortieren
if(info[cur_adapter].modes[temp].Width==temp_mode.Width &&
info[cur_adapter].modes[temp].Height==temp_mode.Height)
{
vorhanden=TRUE;
}
}

if(!vorhanden)
{
memcpy(&(info[cur_adapter].modes[temp_pos]),&temp_mode,sizeof(D3DDISPLAYMODE));
temp_pos++;
}
}

//---------------------------------------------
//16 bit Werte
//---------------------------------------------

for(cur_mode=0;cur_mode<info[cur_adapter].display_mode_count;cur_mode++)
{
lpD3D->EnumAdapterModes(cur_adapter,
D3DFMT_X1R5G5B5,
cur_mode,
&temp_mode);

//Nicht mehr zeitgemäß -> aussortieren
if(temp_mode.Width<640 || temp_mode.Height<480)
{
continue;
}

//Zu große Auflösung -> aussortieren
if(temp_mode.Width>1600 || temp_mode.Height>1200)
{
continue;
}


vorhanden=FALSE;
for(UINT temp=0;temp<temp_pos+1;temp++)
{
//Wenn der Mode schon vorhanden ist -> aussortieren
if(info[cur_adapter].modes[temp].Width==temp_mode.Width &&
info[cur_adapter].modes[temp].Height==temp_mode.Height)
{
vorhanden=TRUE;
}
}

if(!vorhanden)
{
memcpy(&(info[cur_adapter].modes[temp_pos]),&temp_mode,sizeof(D3DDISPLAYMODE));
temp_pos++;
}
}

//Noch mal 16 bit
for(cur_mode=0;cur_mode<info[cur_adapter].display_mode_count;cur_mode++)
{
lpD3D->EnumAdapterModes(cur_adapter,
D3DFMT_R5G6B5,
cur_mode,
&temp_mode);

//Nicht mehr zeitgemäß -> aussortieren
if(temp_mode.Width<640 || temp_mode.Height<480)
{
continue;
}

//Zu große Auflösung -> aussortieren
if(temp_mode.Width>1600 || temp_mode.Height>1200)
{
continue;
}


vorhanden=FALSE;
for(UINT temp=0;temp<temp_pos+1;temp++)
{
//Wenn der Mode schon vorhanden ist -> aussortieren
if(info[cur_adapter].modes[temp].Width==temp_mode.Width &&
info[cur_adapter].modes[temp].Height==temp_mode.Height)
{
vorhanden=TRUE;
}
}

if(!vorhanden)
{
memcpy(&(info[cur_adapter].modes[temp_pos]),&temp_mode,sizeof(D3DDISPLAYMODE));
temp_pos++;
}
}

info[cur_adapter].display_mode_count=temp_pos;

//Verzeichnis der Anwendung
char Verzeichnis[MAX_PATH+1];
GetModuleFileName(0, Verzeichnis,MAX_PATH+1);
*strrchr(Verzeichnis, '\\') = 0;

//User-Datei-Pfad zusammensetzen
char pfad[MAX_PATH+1];
sprintf(pfad,"%s\\user.dat",Verzeichnis);

//Benutzer-Einstellungen-Datei öffnen
user=CreateFile(pfad,GENERIC_READ | GENERIC_WRITE,FILE_SHARE_READ,NULL,OPEN_EXISTING,0,NULL);

if(user==INVALID_HANDLE_VALUE)
{
//Datei ist nicht vorhanden -> neu erstellen

user=CreateFile(pfad,GENERIC_READ | GENERIC_WRITE,FILE_SHARE_READ,NULL,CREATE_ALWAYS,0,NULL);
if(user==INVALID_HANDLE_VALUE)
{
MessageBox(0,"Datei kann nicht erstellt werden",0,0);
return 0;
}

int temp_wert;
DWORD geschriebene_bytes;

temp_wert=-1;

WriteFile(user,&temp_wert,sizeof(int),&geschriebene_bytes,NULL);
WriteFile(user,&temp_wert,sizeof(int),&geschriebene_bytes,NULL);
WriteFile(user,&temp_wert,sizeof(int),&geschriebene_bytes,NULL);
WriteFile(user,&temp_wert,sizeof(int),&geschriebene_bytes,NULL);
}
else
{
int adapter_id;
//ID des Adapters
int adapter_mode;
//ID des Adapter-Modes
int bpp;
//BitsPerPixel des BackBuffer
int zbpp;
//BitsPerPixel des ZBuffer


DWORD gelesene_bytes;

//Werte aus der Datei lesen
ReadFile(user,&adapter_id,sizeof(int),&gelesene_bytes,NULL);
ReadFile(user,&adapter_mode,sizeof(int),&gelesene_bytes,NULL);
ReadFile(user,&bpp,sizeof(int),&gelesene_bytes,NULL);
ReadFile(user,&zbpp,sizeof(int),&gelesene_bytes,NULL);

//Im Dialog auf Adapter bzw. Adaptermode scrollen
SendDlgItemMessage(hwndDlg,IDC_SETGC,CB_SETCURSEL,(WPARAM)adapter_id,0);

LPARAM param;
param=MAKEWPARAM(IDC_SETGC,CBN_SELENDOK);
SendMessage(hwndDlg,WM_COMMAND,param,0);

SendDlgItemMessage(hwndDlg,IDC_SETMODE,CB_SETCURSEL,(WPARAM)adapter_mode,0);

//BPP im Dialog anzeigen
if(bpp==16)
{
SendDlgItemMessage(hwndDlg,IDC_CHECK1,BM_SETCHECK,1,0);
SendDlgItemMessage(hwndDlg,IDC_CHECK2,BM_SETCHECK,0,0);
}
else if(bpp==32)
{
SendDlgItemMessage(hwndDlg,IDC_CHECK1,BM_SETCHECK,0,0);
SendDlgItemMessage(hwndDlg,IDC_CHECK2,BM_SETCHECK,1,0);
}

//Z-Bits im Dialog anzeigen
if(zbpp==16)
{
SendDlgItemMessage(hwndDlg,IDC_CHECK3,BM_SETCHECK,1,0);
SendDlgItemMessage(hwndDlg,IDC_CHECK4,BM_SETCHECK,0,0);
}
else if(zbpp==32)
{
SendDlgItemMessage(hwndDlg,IDC_CHECK3,BM_SETCHECK,0,0);
SendDlgItemMessage(hwndDlg,IDC_CHECK4,BM_SETCHECK,1,0);
}
}

//Datei schließen
CloseHandle(user);
}
return TRUE;

Puh, das war ein ganz schönes Stück Arbeit oder? Wir haben also nun den Dialog erstellt und die Grafikkarten und deren Grafikmodi in ein Array gepresst. Nun müssen wir nur noch dafür sorgen, dass bei Neuauswahl der Grafikkarte das Modi Array geleert und für die selektierte Karte die Einträge hinzugefügt werden. Das ist aber eine ganz einfache Aufgabe und in ein paar Zeilen erledigt, die als Case-Zweig in der Callback-Funktion eingefügt werden:

case WM_COMMAND:
switch(HIWORD(wParam))
{
case CBN_SELENDOK:
switch(LOWORD(wParam))
{
case IDC_SETGC:
char mode_text[100];
int auswahl;
UINT cur_mode;

//Inhalt der Grafikmodi-ComboBox löschen
SendDlgItemMessage(hwndDlg,IDC_SETMODE,CB_RESETCONTENT,0,0);

//Auswahl der ComboBox holen
auswahl=SendDlgItemMessage(hwndDlg,IDC_SETGC,CB_GETCURSEL,0,0);

for(cur_mode=0;cur_mode<info[auswahl].display_mode_count;cur_mode++)
{
sprintf(mode_text,"%d x%d",info[auswahl].modes[cur_mode].Width,info[auswahl].modes[cur_mode].Height);

SendDlgItemMessage(hwndDlg,IDC_SETMODE,CB_ADDSTRING,0,(LPARAM)(LPCTSTR)mode_text);
}
}
}
return TRUE;

Funktioniert doch schon richtig gut :-). Was jetzt noch fehlt ist die Nachrichtenbehandlung für den Start Button und für die Kontrollkästchen. Doch ohne große Vorrede der Code, welcher mal wieder länger, aber nicht komplizierter ist und in die soeben erstellte WM_COMMAND-Case eingefügt wird:

switch(LOWORD(wParam))
{
//Bei Auswahlen der Check-Controls das andere "Check" 'unchecken'
case IDC_CHECK1:
SendDlgItemMessage(hwndDlg,IDC_CHECK2,BM_SETCHECK,0,0);
return TRUE;
break;

case IDC_CHECK2:
SendDlgItemMessage(hwndDlg,IDC_CHECK1,BM_SETCHECK,0,0);
return TRUE;
break;

case IDC_CHECK3:
SendDlgItemMessage(hwndDlg,IDC_CHECK4,BM_SETCHECK,0,0);
return TRUE;
break;

case IDC_CHECK4:
SendDlgItemMessage(hwndDlg,IDC_CHECK3,BM_SETCHECK,0,0);
return TRUE;
break;
//----------------------------------------------------------------

case IDC_START:
//Werte in die ADAPTERSELECT Struktur schreiben
int auswahl;
auswahl=SendDlgItemMessage(hwndDlg,IDC_SETGC,CB_GETCURSEL,0,0);
if(auswahl==-1)
{
MessageBox(0,"Bitte Grafikkarte auswählen",0,0);
return TRUE;
}

if(SendDlgItemMessage(hwndDlg,IDC_SETMODE,CB_GETCURSEL,0,0)==-1)
{
MessageBox(0,"Bitte Auflösung auswählen",0,0);
return TRUE;
}

adapter.screen_width=info[auswahl].modes[SendDlgItemMessage(hwndDlg,IDC_SETMODE,CB_GETCURSEL,0,0)].Width;
adapter.screen_height=info[auswahl].modes[SendDlgItemMessage(hwndDlg,IDC_SETMODE,CB_GETCURSEL,0,0)].Height;

adapter.adapter=auswahl;

if(SendDlgItemMessage(hwndDlg,IDC_CHECK1,BM_GETCHECK,0,0)==BST_CHECKED)
{
adapter.screen_bpp=16;
}
else if(SendDlgItemMessage(hwndDlg,IDC_CHECK2,BM_GETCHECK,0,0)==BST_CHECKED)
{
adapter.screen_bpp=32;
}
else
{
MessageBox(0,"Bitte Farbtiefe angeben",0,0);
return TRUE;
}

if(SendDlgItemMessage(hwndDlg,IDC_CHECK3,BM_GETCHECK,0,0)==BST_CHECKED)
{
adapter.screen_z_bits=16;
}
else if(SendDlgItemMessage(hwndDlg,IDC_CHECK4,BM_GETCHECK,0,0)==BST_CHECKED)
{
adapter.screen_z_bits=32;
}
else
{
MessageBox(0,"Bitte Z-Buffer-Bittiefe angeben",0,0);
return TRUE;
}

//Verzeichnis der Anwendung
char Verzeichnis[MAX_PATH+1];
GetModuleFileName(0, Verzeichnis,MAX_PATH+1);
*strrchr(Verzeichnis, '\\') = 0;

//User-Datei-Pfad zusammensetzen
char pfad[MAX_PATH+1];
sprintf(pfad,"%s\\user.dat",Verzeichnis);

//Benutzer-Einstellungen-Datei öffnen
user=CreateFile(pfad,GENERIC_READ | GENERIC_WRITE,FILE_SHARE_READ,NULL,CREATE_ALWAYS, 0,NULL);
if(user==INVALID_HANDLE_VALUE)
{
MessageBox(0,"Die Benutzen-Datei kann nicht geöffnet werden",0,0);
}

int temp_wert;
DWORD geschriebene_bytes;

//Die gewählete Grafikkarte schreiben
temp_wert=SendDlgItemMessage(hwndDlg,IDC_SETGC,CB_GETCURSEL,0,0);
WriteFile(user,&temp_wert,sizeof(int),&geschriebene_bytes,NULL);

//Die gewählete Auflösung schreiben
temp_wert=SendDlgItemMessage(hwndDlg,IDC_SETMODE,CB_GETCURSEL,0,0);
WriteFile(user,&temp_wert,sizeof(int),&geschriebene_bytes,NULL);

//Die gewählete Farbtiefe schreiben
temp_wert=(int)adapter.screen_bpp;
WriteFile(user,&temp_wert,sizeof(int),&geschriebene_bytes,NULL);

//Die gewählete Z-Bittiefe schreiben
temp_wert=(int)adapter.screen_z_bits;
WriteFile(user,&temp_wert,sizeof(int),&geschriebene_bytes,NULL);

CloseHandle(user);

EndDialog(hwndDlg,0);
break;
}

Geschafft! Nur noch ein:

return FALSE;
}

..., damit der Dialog richtig arbeitet und die Grafikkartenauswahl ist soweit fertig. Jetzt müssen wir sie nur noch vom Hauptprogramm aus aufrufen:

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,int nCmdShow)
{
//Direct3D9 Objekt erstellen
if(FAILED(lpD3D=Direct3DCreate9(D3D_SDK_VERSION)))
{
MessageBox(0,"DirectX9 kann nicht initialisiert werden",0,0);
return -1;
}

//Dialog öffnen
DialogBox(hInstance,MAKEINTRESOURCE(IDD_SELECT),NULL,SelectProc);

char ausgabe[200];
sprintf(ausgabe,"AdapterID: %d\nAuflösung: %d x %d\nFarbtiefe: %d\nZ-Bits: %d",
adapter.adapter,adapter.screen_width,adapter.screen_height,adapter.screen_bpp,
adapter.screen_z_bits);
MessageBox(0,ausgabe,"Zusammenfassung",0);

//Direct3D9 Objekt freigeben
lpD3D->Release();
lpD3D=NULL;

//Kein Fehler aufgetreten
return 0;
}

Der Dialog sollte in Aktion ca. so aussehen:

Hiermit beende ich auch dieses kleine Tutorial und hoffe, dass ich euch helfen konnte eine Next-Generation-Grafikkartenauswahl für euer Spiel zu erstellen.

Download des Programmcodes

Bei Fragen oder für Anregungen schreibt mir bitte unter webmaster@manuel-then.de oder schaut auf http://www.manuel-then.de

M.T.