Du bist nicht angemeldet.

Stilllegung des Forums
Das Forum wurde am 05.06.2023 nach über 20 Jahren stillgelegt (weitere Informationen und ein kleiner Rückblick).
Registrierungen, Anmeldungen und Postings sind nicht mehr möglich. Öffentliche Inhalte sind weiterhin zugänglich.
Das Team von spieleprogrammierer.de bedankt sich bei der Community für die vielen schönen Jahre.
Wenn du eine deutschsprachige Spieleentwickler-Community suchst, schau doch mal im Discord und auf ZFX vorbei!

Werbeanzeige

BlazeX

Alter Hase

  • »BlazeX« ist der Autor dieses Themas

Beiträge: 478

Wohnort: DD

Beruf: Maschinenbau-Student

  • Private Nachricht senden

1

19.02.2010, 14:25

Direct3D11 Settings Dialog - Teil 1 - Enumerieren

Direct3D11 Settings Dialog - Teil 1 - Enumerieren

Teil 1: Den liest du gerade ;)
Teil 2: Link
Teil 3: Link

Inhalt

1. Einleitung
2. Abhängigkeiten
3. DXGI und D3D - Jetzt wird enumeriert!
3.1. Adapter und Output
3.2. Auflösung
3.3. VSync und Fullscreen
3.4. Multisampling
3.5. Anisotropische Filterung
4. Demo zum herunterladen
5. Ausblick

1. Einleitung

Hi Leute!
Ohne große Worte: Ich zeige euch, wie man einen Settings-Dialog für Direct3D11 schreibt.
So soll er mal aussehen:


(Link)


Da ich nicht alles in 1 Tutorial packen will, hab ich es in 3 Stück gegliedert:
Teil 1 - Enumerieren: Hier geht es darum, wie man alles nötige vom Grafikadapter bis zur passenden Auflösung aufzählt.
Teil 2 - Der Tabbed Dialog: Die Grundlage eines Dialogs mit Tabs.
Teil 3 - Eine Video-Page für den Dialog: 1 + 2 = 3

Man sollte unbedingt während dem Lesen dieses Tutorials die DirectX Graphics Documentation geöffnet haben, um eventuelle Parameter nachschlagen zu können.

2. Abhängigkeiten

Wie auch in D3D9 ist auch in D3D11 das eine vom anderen Abhängig. Zum Beispiel kann man eine Fullscreen Auflösung von 1920x1080 nicht von einem 15"er erwarten.
Die Auflösung bestimmt der Monitor. Und wer bestimmt den Monitor? Wovon ist Multisampling abhängig?
Hier eine Übersicht dazu:


(Link)


Man sieht gewisse Parallelen zum Settings-Dialog.

3. DXGI und D3D - Jetzt wird enumeriert!
3.1. Adapter und Output
BTW: "enumerieren" bedeutet so viel wie Aufzählen.

Seit Direct3D10 hat sich einiges geändert. Die DXGI (DirectX Graphics Infrastructure) kam hinzu und löste das IDirect3D9-Interface ab.
Man wird also alles mit der DXGI aufzählen und D3D nur noch zum Rendern benutzen, bis auf Multisampling..., aber dazu später mehr.
Um die DXGI nutzen zu können, braucht man üblicherweise eine Art "Start-Interface" von dem aus man alles weiter bekommt.
In dem Fall heißt es IDXGIFactory1. Es ist ein COM-Interface, also Release nicht vergessen ;).
Und so wird es erzeugt:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
IDXGIFactory1 * pFactory;
HRESULT hResult= CreateDXGIFactory1(__uuidof(IDXGIFactory1), (void**)(&pFactory) );
if(FAILED(hResult))
{
    //...
}

//...

pFactory->Release();


Die nötigen Header und Libs bindet man wie folgt ein:

C-/C++-Quelltext

1
2
3
4
5
#include<DXGI.h> 
#include<D3D11.h> 

#pragma comment(lib, "DXGI.lib") 
#pragma comment(lib, "D3D11.lib")


An dieses __uuidof wird man sich gewöhnen müssen. Es kommt ständig, wenn man neue Interfaces haben möchte.

Mit der "Factory" kann man die "Adapter" aufzählen. Ein Adapter stellt eine Grafikkarte bzw. einen Grafikchip dar.
So zählt man alle vorhandenen Adapter (Auch die, die kein D3D11 unterstützen!) auf:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//Alle Adapter (Grafikkarten/-chips) auflisten.
DWORD dwAdapter= 0; 
IDXGIAdapter1 * pAdapter;
while(pFactory->EnumAdapters1(dwAdapter++, &pAdapter) != DXGI_ERROR_NOT_FOUND) 
{
    //Adapter gefunden.
    //Desc abfragen.
    DXGI_ADAPTER_DESC1 AdapterDesc;
    pAdapter->GetDesc1(&AdapterDesc);

    //Name des Adapter (Achtung Unicode!) ausgeben
    MessageBoxW(NULL, AdapterDesc.Description, L"Adapter gefunden!", MB_OK|MB_ICONINFORMATION);

    //Adapter freigeben
    pAdapter->Release();
}


Wieso steht da überall eine 1 dahinter? Die 1 symbolisiert DXGI 1.1 - Die Neuauflage, passend zu DX11.
Nach Möglichkeit sollte man die 1er Intefaces und Funktionen benutzen.
Wem interessiert, was es alles als 1er gibt, der sollte in der DirectX Graphics Documentation mal den Teil mit der DXGI Reference nachschlagen.

Es kann sein, dass man 2 oder mehr Grafikkarten in einem System hat. Meistens hat man noch so einen lächerlichen OnBoard-Chip.
Jeder der "Adapter" hat eine Nummer. 0 ist der primäre Adapter, 1 der zweite usw.
Wenn man alle abfragen möchte, fängt man am besten bei 0 an und erhört die Nummer solange um 1, bis kein Adapter mit dieser Nummer mehr gefunden wird. Siehe Beispiel.

An so einer Grafikkarte hängt normalerweise ein Monitor (Wenn es nicht gerade Teslas sind).
Dieser wird als "Output" bezeichnet und kann mit IDXGIAdapter1::EnumOutputs abgefragt werden.

Das funktioniert fast genauso wie mit dem Adapter:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//Outputs auflisten
DWORD dwOutput= 0;
IDXGIOutput * pOutput;
while(pAdapter->EnumOutputs(dwOutput++, &pOutput) != DXGI_ERROR_NOT_FOUND)
{
    //Output-Desc abfragen
    DXGI_OUTPUT_DESC OutputDesc;
    pOutput->GetDesc(&OutputDesc);
    
    //Name des Outputs (Achtung Unicode!) ausgeben
    MessageBoxW(NULL, OutputDesc.Description, L"Output gefunden!", MB_OK|MB_ICONINFORMATION);
    
    //Output freigeben
    pOutput->Release();
}


Das verschachtelt sich ganz schön und wird noch schlimmer, wenn man da keine Ordnung rein bekommt.
Aus diesem Grund habe ich 2 Funktionen geschrieben, die einem die Arbeit erleichtern: Die eine zählt Adapter auf, die andere Outputs.
Aber wie "speichert" man die Adapter und Outputs? In einer Liste! Genauso wie alles andere.
Für einen Adapter und ein Output sind 2 Dinge wichtig:
Der Name - Für den Benutzer.
Die ID - Für das Programm.
Also werden kurzerhand 2 Strukturen ins Leben gerufen:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
//Ein Adapter
struct Adapter
{
    DWORD dwID;
    char Name[128];
};

//Ein Output
struct Output
{
    DWORD dwID;
    char Name[32];
};


Die Funktionen müssten also eine Liste mit Adaptern und Outputs füllen. Dabei wird eine std::list verwendet.
Hier ist die Funktion, die die Adapter enumeriert:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//Listet alle verfügbaren Grafik-Adapter auf.
void ListAdapters(IDXGIFactory1 * pFactory, std::list<Adapter> * pToFill)
{
    //Parameter überprüfen
    if(!pFactory || !pToFill)
        LOG_ERROR_AND_THROW("Invalid parameters!");

    //Liste leeren
    pToFill->clear();

    DWORD dwAdapter= 0;
    IDXGIAdapter1 * pAdapter;
    while(pFactory->EnumAdapters1(dwAdapter++, &pAdapter) != DXGI_ERROR_NOT_FOUND) 
    {
        //Adapter gefunden, Desc abfragen.
        DXGI_ADAPTER_DESC1 AdapterDesc;
        TRY_DX(pAdapter->GetDesc1(&AdapterDesc));

        //Neuen Eintrag erstellen
        Adapter Entry;
        Entry.dwID= dwAdapter - 1;

        //Adaptername konvertieren
        DWORD dwLength= wcslen(AdapterDesc.Description);
        WideCharToMultiByte(CP_ACP, 0,
                AdapterDesc.Description, dwLength+1,
                Entry.Name, dwLength+1, NULL, NULL);

        //Hinzufügen
        pToFill->push_back(Entry);

        //Adapter freigeben
        pAdapter->Release();
    }

    //Erfolg gehabt?
    if(pToFill->empty())
        LOG_ERROR_AND_THROW("Enumeration failed! List is empty.");
}


Dieses Makro LOG_ERROR_AND_THROW stammt aus meiner Engine und schreibt nur den angegeben String in ein Logbuch und wirft eine Exception.
Das Makro TRY_DX stammt ebenfalls aus meine Engine, und ist wie folgt definiert:

C-/C++-Quelltext

1
2
3
4
5
#define TRY_DX(Function)        {   HRESULT hResult= Function;  if(FAILED(hResult))     \
    {   GT::Core::Log::Instance()->LogDXError(GT::Core::Log::                           \
    GetDebugInfo(__FUNCTION__, __LINE__, __FILE__), hResult, #Function);                \
    LOG_ERROR("DirectX caused an error!");                                              \
    throw(std::exception("DirectX caused an error! See Log for more Information."));    }   }

Der Kern ist:

C-/C++-Quelltext

1
2
3
4
5
HRESULT hResult= Function;
if(FAILED(hResult))
{
    //...
}


Diese Kontrolle ist typisch für DirectX-Funktionen. Das Makro hab ich nur wegen das Komforts angelegt.
Es prüft, ob die Funktion einen Fehler verursacht und reagiert entsprechend.

Die Funktion ListAdapters erwartet also ein Factory Objekt und eine Liste, die es füllen soll.
Zu Beginn prüft sie die Parameter auf Null-Pointer und leert die Liste (der alte Inhalt könnte das Ergebnis verfälschen).
Dann werden Alle Adapter der Reihe nach durchgegangen und die ID und der Name wird in der Liste verewigt. Der Name wird noch in ASCII Text umgewandelt.
Wenn keine Adapter gefunden wurden, wird eine Exception geworfen. Normalerweise passiert sowas nicht, aber man weiß ja nie.

Die Funktion ListOutputs sieht fast haargenau so aus und ich denke mal, dass ich dazu nichts weiter sagen muss:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
//Outputs auflisten
void ListOutputs(IDXGIFactory1 * pFactory, DWORD dwAdapter, std::list<Output> * pToFill)
{
    //Parameter überprüfen
    if(!pFactory || !pToFill)
        LOG_ERROR_AND_THROW("Invalid parameters!");

    //Liste leeren
    pToFill->clear();

    //Adapter anhand der ID holen
    IDXGIAdapter1 * pAdapter;
    TRY_DX(pFactory->EnumAdapters1(dwAdapter, &pAdapter));

    //Outputs auflisten
    DWORD dwOutput= 0;
    IDXGIOutput * pOutput;
    while(pAdapter->EnumOutputs(dwOutput++, &pOutput) != DXGI_ERROR_NOT_FOUND)
    {
        //Output-Desc abfragen
        DXGI_OUTPUT_DESC OutputDesc;
        TRY_DX(pOutput->GetDesc(&OutputDesc));

        //Neuen Eintrag erstellen
        Output Entry;
        Entry.dwID= dwOutput - 1;

        //Name komvertieren
        DWORD dwLength= wcslen(OutputDesc.DeviceName);
        WideCharToMultiByte(CP_ACP, 0,
                OutputDesc.DeviceName, dwLength+1,
                Entry.Name, dwLength+1, NULL, NULL);

        //Hinzufügen
        pToFill->push_back(Entry);
        
        //Output freigeben
        pOutput->Release();
    }

    //Erfolg gehabt?
    if(pToFill->empty())
        LOG_ERROR_AND_THROW("Enumeration failed! List is empty.");
}


2 Dinge geschafft!
Und so benutzt man die Funktionen jetzt:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, char * pcCommandLine, int iShowCommand)
{
    try
    {
        //Factory erzeugen
        IDXGIFactory1 * pFactory;
        TRY_DX(CreateDXGIFactory1(__uuidof(IDXGIFactory1), (void**)(&pFactory)));
        
        //Adapter auflisten
        std::list<Adapter> Adapters;
        ListAdapters(pFactory, &Adapters);
        for(std::list<Adapter>::iterator Adapter= Adapters.begin(); Adapter != Adapters.end(); Adapter++)
        {
            //Outputs auflisten
            std::list<Output> Outputs;
            ListOutputs(pFactory, Adapter->dwID, &Outputs);
            for(std::list<Output>::iterator Output= Outputs.begin(); Output != Outputs.end(); Output++)
            {
                //Ausgeben
                MessageBox(NULL, Output->Name, Adapter->Name, MB_OK|MB_ICONINFORMATION);
            }
        }

        pFactory->Release();
    }
    catch(std::exception & Err)
    {
        MessageBox(NULL, Err.what(), "Error", MB_OK|MB_ICONERROR);
        return(1);
    }

    return(0);
}


... Diese Liste mit den Adapter könnte man doch in eine ComboBox stecken ... Wenn der Benutzer dann eine Adapter wählt, werden die passenden Outputs angezeigt...

Ich hoffe, ihr habt eben auch an sowas gedacht. Wenn nicht: Mitdenken! Wenn doch: Gut, weiter so!

3.2. Auflösung
Von D3D9 kennt ihr sicher noch diese VideoModes.
Seit DXGI kam aber noch etwas mehr dazu:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
typedef struct DXGI_MODE_DESC {
    UINT Width;
    UINT Height;
    DXGI_RATIONAL RefreshRate;
    DXGI_FORMAT Format;
    DXGI_MODE_SCANLINE_ORDER ScanlineOrdering;
    DXGI_MODE_SCALING Scaling;
} DXGI_MODE_DESC, *LPDXGI_MODE_DESC;


Früher hatte man dann sowas zur Auswahl:

Quellcode

1
2
3
4
5
6
7
8
9
10
800x600 @59Hz 16Bit
800x600 @59Hz 32Bit
800x600 @60Hz 16Bit
800x600 @60Hz 32Bit
800x600 @62Hz 16Bit
800x600 @62Hz 32Bit
800x600 @70Hz 16Bit
800x600 @70Hz 32Bit
800x600 @75Hz 16Bit
800x600 @75Hz 32Bit

Kommt da jetzt noch mehr dazu?
Muss das sein? Ich finde sowas schrecklich!
Die Farbtiefe von heute ist doch 32 Bit! (Mal sehen ob irgendwann HDR-Monitore rauskommen.)
Also kann man sich doch auf ein Format festlegen.
Ich verwende von Anfang an das Format DXGI_FORMAT_R8G8B8A8_UNORM
Es beschreibt 32 Bit pro Pixel. 8 Bit je Kanal.
Das UNORM heißt nichts anderes als, dass die Farbwerte in der Datei (wahrscheinlich auch im Speicher) als Zahl von 0-255 vorliegt. Im Shader werden aber Zahlen zwischen 0-1 benutzt. Die Konvertierung erfolgt automatisch.

Wen interessiert die RefreshRate? Am besten so groß wie möglich und gut. Dafür gibt es später auch eine Funktion.
Für die Scanline Order und das Scaling gibt es aber zum Glück "Standardwerte" (0), bei denen automatisch etwas passendes benutzt wird.
Jetzt ist der VideoMode ja soweit kastriert, dass er nur noch aus Auflösung besteht. Der Rest wird passend gemacht.

Gut. Jetzt wissen wir, was ein VideoMode ist und wie man ihn kastriert, aber woher bekommt man ihn jetzt?
Denkpause...
Richtig! Vom Output. Es war auch so schwer, mal die Skizze von den Abhängigkeiten anzuschauen ;).
Die Methode heißt: IDXGIOutput::GetDisplayModeList
Sie verlangt ein Format, Flags, die Anzahl der Modes und einen Speicherort für die Modes.
Das Format soll DXGI_FORMAT_R8G8B8A8_UNORM sein, das hab ich mal so festgelegt.
Mit den Flags erreicht man nur, dass Modes ausgegeben werden, die der Monitor nur mit Kopfstand erreichen kann -> Das lassen wir lieber.

Woher bekommt man jetzt die Anzahl der Modes? Ein kurzer Blick in die Doku ... gefunden!
Als Speicherort NULL angeben und schon wird die Anzahl aller verfügbaren Modi zurückgegeben.
Tada:

C-/C++-Quelltext

1
2
3
//Anzahl der VideoModes abfragen
DWORD dwNumModes;
pOutput->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, 0, (UINT*)&dwNumModes, NULL);


Jetzt muss man genügend Speicher reservieren und die Funktion richtig aufrufen:

C-/C++-Quelltext

1
2
3
//Speicher für alle VideoModes reservieren und alle abragen
DXGI_MODE_DESC * pVideoModes= new DXGI_MODE_DESC[dwNumModes];
pOutput->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, 0, (UINT*)&dwNumModes, pVideoModes);


Jetzt hat man eine Liste von schätzungsweise 100 VideoModes. Gut oder? Nein! Vor allem nicht, wenn darunter gerademal 12 verschiedene Auflösungen stecken.
Also muss man filtern. Dazu geht man alle VideoModes durch und packt die Aufllösungen in eine Liste.
Moment mal. Eine Auflösung? Bitte:

C-/C++-Quelltext

1
2
3
4
5
//Auflösung
struct Resolution
{
    DWORD dwX, dwY;
};


Wie gesagt. Alles in eine Liste:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
//Alle VideoModes durchgehen und in der Liste speichern
for(DWORD dwMode= 0; dwMode < dwNumModes; dwMode++)
{
    //Neuen Eintrag hinzufügen
    Resolution Entry;
    Entry.dwX= pVideoModes[dwMode].Width;
    Entry.dwY= pVideoModes[dwMode].Height;
    pToFill->push_back(Entry);
}


Jetzt würde die Liste in etwa so aussehen:

Quellcode

1
2
3
4
5
6
7
8
9
10
11
640x480
640x480
640x480
640x480
640x480
640x480
800x600
800x600
800x600
800x600
...

Viel zu lang! Also alle Doppelten entfernen und danach nochmal sortieren:

C-/C++-Quelltext

1
2
3
//Alle doppelten raus und sortieren
pToFill->unique();
pToFill->sort();


In diesem Fall würde aber der Compiler streiken. Woher soll er denn auch wissen, welche "Resolution" größer als die andere ist? Wie soll er sie vergleichen?
Zum Glück ist die STL (std::list) da genügsam und holt alle Vergleiche aus den Operatoren == und <. Ein a > b ist dann eben ein ! a < b.
So hilft man da aus:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
//Vergleich von Auflösungen für std::list nötig
bool operator == (const Resolution& a, const Resolution& b)
{
    return(a.dwX == b.dwX && a.dwY == b.dwY);
}
bool operator < (const Resolution& a, const Resolution& b)
{
    return(a.dwX == b.dwX ? a.dwY < b.dwY : a.dwX < b.dwX);
}

Eine genaue Übereinstimmung hat man also wenn beide X und beide Y gleich sind.
Beim operator < hat man 2 Werte, von denen der eine kleiner sein kann als der andere.
Man könnte X1*Y1 < X2*Y2 zurückgeben und eben nach der Fläche sortieren.
Aber ich halte es für sinvoller, alle Auflösungen erst nach der Breite und dann nach der Höhe zu sortieren. Das sieht alphabetischer aus.

Und schon hat man alles für eine weitere List Funktion fertig! Seht selbst:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
//Listet die bevorzugten Auflösungen (Vorgabe: 32-Bit Farbtiefe) auf
void ListResolutions(IDXGIFactory1 * pFactory, DWORD dwAdapter, DWORD dwOutput, std::list<Resolution> * pToFill)
{
    //Parameter überprüfen
    if(!pFactory || !pToFill)
        LOG_ERROR_AND_THROW("Invalid parameters!");

    //Adapter und Output holen
    IDXGIAdapter1 * pAdapter;
    TRY_DX(pFactory->EnumAdapters1(dwAdapter, &pAdapter));
    IDXGIOutput * pOutput;
    TRY_DX(pAdapter->EnumOutputs(dwOutput, &pOutput));

    //Anzahl der VideoModes abfragen
    DWORD dwNumModes;
    pOutput->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, 0, (UINT*)&dwNumModes, NULL);

    //Speicher für alle VideoModes reservieren und alle abragen
    DXGI_MODE_DESC * pVideoModes= new DXGI_MODE_DESC[dwNumModes];
    pOutput->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, 0, (UINT*)&dwNumModes, pVideoModes);

    //Alle VideoModes durchgehen und in der Liste speichern
    for(DWORD dwMode= 0; dwMode < dwNumModes; dwMode++)
    {
        //Neuen Eintrag hinzufügen
        Resolution Entry;
        Entry.dwX= pVideoModes[dwMode].Width;
        Entry.dwY= pVideoModes[dwMode].Height;
        pToFill->push_back(Entry);
    }

    //Alle doppelten raus und sortieren
    pToFill->unique();
    pToFill->sort();

    //Alles freigeben
    delete[] pVideoModes;
    pOutput->Release();
    pAdapter->Release();

    //Erfolg gehabt?
    if(pToFill->empty())
        LOG_ERROR_AND_THROW("Enumeration failed! List is empty.");
}

Die übliche Fehlerkontrolle findet wieder statt.
Ein weiterer Komfort ist es, "von außen" nicht mehr mit Interfaces für Adapter und Output arbeiten zu müssen. Man übergibt einfach nur die Nummer des Adapters (und Outputs) - die man wunderbar in eine Datei speichern kann - an die Funktion und sie erledigt den Rest.

Damit hätten wir die Auflösung auch abgeschlossen.
Wer es selber ausprobieren möchte, sollte mal ein Programm schreiben (Konsole am besten geeignet), dass:
1. Alle Adapter auflistet und den Benutzer einen aussuchen lässt.
2. Alle Outputs des Adapters auflistet und den Benutzer einen aussuchen lässt.
3. Alle Auflösungen des Outputs ausgibt.

Aber nochmal zurück zur RefreshRate. Die war ja nicht als DWORD, float, int oder etwas ähnlich verständlichem angegeben. Nein, MS musste das unbedingt als rationale Zahl (DXGI_RATIONAL) machen.
Das ist eigentlich nur ein "gemeiner Bruch". Ich erinnere mich gerade daran, wie unser Leher uns das Bruchrechnen beibrachte: Er hatte einen Zuckerkuche mitgebracht und zeigte, was ein halb, ein Viertel usw. war. Lecker!
Zurück zum Thema.
Die Refreshrate kennt man heute eigentlich nicht als 595000 / 1000, sondern eher als 59.
Also wird kurzerhand die übliche Zahl berechnet:

C-/C++-Quelltext

1
DWORD dwRefreshRate= RefreshRate.Numerator / RefreshRate.Denominator;

Keine Angst, wenn man später wieder die Auflösung als rationale Zahl angeben soll, benutzt man einfach diese Formel:

C-/C++-Quelltext

1
2
RefreshRate.Numerator= dwRefreshRate;
RefreshRate.Denominator= 1;

Kleine Rundungsfehler werden locker weggesteckt. Das passt schon.

Wie gesagt, will ich jedesmal gleich die höchste RefreshRate benutzen (Was spricht denn dagegen?).
Aber wie findet man die heraus?
Zuerstmal muss geklärt werden, wovon sie abhängig ist: Von der Auflösung (damit auch von Output und Adapter).
Man würde also wieder alle VideoModes (nicht nur die Aufllösungen!) aufzählen und nach der gegebenen Auflösung suchen.
Wenn man alle passenden VideoModes gefunden hat, sucht man sich den mit der größten RefreshRate und gibt diese zurück.
Das ganze sieht folgendermaßen aus:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
//Höchstmögliche RefreshRate finden
DWORD FindMaxRefreshRate(IDXGIFactory1 * pFactory, DWORD dwAdapter, DWORD dwOutput, int dwBBSizeX, int dwBBSizeY)
{
    //Adapter und Output holen
    IDXGIAdapter1 * pAdapter;
    pFactory->EnumAdapters1(dwAdapter, &pAdapter);
    IDXGIOutput * pOutput;
    pAdapter->EnumOutputs(dwOutput, &pOutput);

    //Anzahl der VideoModes abfragen
    DWORD dwNumModes;
    pOutput->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, 0, (UINT*)&dwNumModes, NULL);

    //Speicher für alle VideoModes reservieren und alle abragen
    DXGI_MODE_DESC * pVideoModes= new DXGI_MODE_DESC[dwNumModes];
    pOutput->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, 0, (UINT*)&dwNumModes, pVideoModes);

    //Alle VideoModes durchgehen
    DWORD dwMaxRefreshRate= 0;
    for(DWORD dwMode= 0; dwMode < dwNumModes; dwMode++)
    {
        //Passt die Auflösung?
        if(pVideoModes[dwMode].Width == dwBBSizeX
        && pVideoModes[dwMode].Height== dwBBSizeY)
        {
            DWORD dwModeRefreshRate= pVideoModes[dwMode].RefreshRate.Numerator / pVideoModes[dwMode].RefreshRate.Denominator;
            dwMaxRefreshRate= Math::TMax(dwMaxRefreshRate, dwModeRefreshRate);
        }
    }

    //Alles
    delete[] pVideoModes;
    pOutput->Release();
    pAdapter->Release();

    //Nichts gefunden? Exception!
    if(dwMaxRefreshRate == 0)
        LOG_ERROR_AND_THROW("No Video Mode of the Output matches to the Resolution!");

    return(dwMaxRefreshRate);
}

Math::TMax ist eine typische max-Funktion: Vergleicht und gibt den größeren Wert zurück.

Vorblick:Beim erstellen des Devices und des SwapChains gibt man einfach folgende Werde für den VideoMode an:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
//SwapChainDesc ausfüllen
//...
SwapChainDesc.BufferDesc.Format             = DXGI_FORMAT_R8G8B8A8_UNORM;
SwapChainDesc.BufferDesc.Width              = Settings.dwBBSizeX;
SwapChainDesc.BufferDesc.Height             = Settings.dwBBSizeY;
SwapChainDesc.BufferDesc.Scaling            = DXGI_MODE_SCALING_UNSPECIFIED;
SwapChainDesc.BufferDesc.ScanlineOrdering       = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
SwapChainDesc.BufferDesc.RefreshRate.Denominator    = 1;
SwapChainDesc.BufferDesc.RefreshRate.Numerator      = FindMaxRefreshRate(pFactory, Settings.dwAdapter, Settings.dwOutput, Settings.dwBBSizeX, Settings.dwBBSizeY);
//...


Damit wäre der "Linke Zweig der Abhängigkeiten" abgehakt.

3.3. VSync und Fullscreen

Was VSync ist, sollte klar sein. Es gibt nur an oder aus. Wer es benutzen will, sollte sich ID3DXSwapChain::Present anschauen.

Ob Fullscreen oder nicht - genau das gleiche: an oder aus. Davon ist nichts weiter abhängig im Gegensatz zu D3D9. Da gab es einige Formate nicht, wenn das Ding im Fenster lief.
Um Fullscreen zu aktivieren benutzt man IDXGISwapChain::SetFullscreenState. Man ruft sie auf, nachdem man das Device und den SwapChain erstellt hat (im Windowed Mode!). Man übergibt ihr ein IDXGIOutput-Interface.
Dazu kommt noch, dass hier die Auflösung des BackBuffers passen MUSS. Im Windowed ist das nicht so wichtig.
BTW: Wenn man ein ID3D11Device erstellt und einen speziellen Adapter wünscht, MUSS man als DriverType D3D_DRIVER_TYPE_UNKNOWN angeben.


3.4. Multisampling
Multisampling ist nur abhängig vom Adapter und nimmt eine Sonderrolle ein: Mit DXGI hat man keine Chance etwas darüber rauszubekommen.
Die einzige Möglichkeit ist, ein ID3D11Device zu erzeugen: Ein "Dummy-Device".
Dafür braucht man nur den Adapter zu kennen.
Also ran an den Speck:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//Adapter holen
IDXGIAdapter1 * pAdapter;
TRY_DX(pFactory->EnumAdapters1(dwAdapter, &pAdapter));

//Ein Dummy-Device erstellen
D3D_FEATURE_LEVEL aWantedFeatureLevel[]=
{
    D3D_FEATURE_LEVEL_11_0,
    D3D_FEATURE_LEVEL_10_1,
    D3D_FEATURE_LEVEL_10_0,
    D3D_FEATURE_LEVEL_9_3,
    D3D_FEATURE_LEVEL_9_2
};
D3D_FEATURE_LEVEL CreatedFeatureLevel;
ID3D11Device * pDevice;
ID3D11DeviceContext * pDeviceContext;
TRY_DX(D3D11CreateDevice(pAdapter,
                          D3D_DRIVER_TYPE_UNKNOWN,
                          NULL,
                          0,
                          aWantedFeatureLevel,
                         sizeof(aWantedFeatureLevel) / sizeof(D3D_FEATURE_LEVEL),
                          D3D11_SDK_VERSION,
                         &pDevice,
                          &CreatedFeatureLevel,
                         &pDeviceContext));


Es wird versucht ein möglichst "gutes" Device (viele Features) für den gewählten Adapter zu erstellen.
Allerding hab ich hier mal das Feature Level 9_1 rausgenommen, da es doch etwas beschränkt ist.
Dass dabei ein "ImmediateDeviceContext" erstellt wird, soll nicht stören, der wird einfach wieder freigegeben.

Um jetzt alle Multisampling-Möglichkeiten zu enumerieren, ruft man die Methode ID3D11Device::CheckMultisampleQualityLevels auf. Sie erwartet das BackBuffer Format (DXGI_FORMAT_R8G8B8A8_UNORM), den SampleCount und einen Ort, wo sie die QualityLevels abspeichern kann.
Interessant ist allerdings nur der SampleCount, denn damit beschreibt man zum Beispiel "8fach MSAA".
Die Quality-Levels sind nicht so wichtig, ich nehme einfach den höchsten. dazu bedarf es später wieder einer "Finder"-Funktion (später mehr).

Jedenfalls überprüft man für den entsprechenden SampleCount die Qualitätsstufen. Und wie macht man das jetzt?
Antwort: Alle möglichen SampleCounts durchgehen und einzeln auf Unterstützung prüfen.
Alle möglichen SampleCounts bedeutet: Alles von 0 bis D3D11_MAX_MULTISAMPLE_SAMPLE_COUNT (=32).
Ob jetzt der SampleCount unterstützt wird, zeigen die QualityLevels. Wenn sie 0 betragen, ist der SampleCount nicht verfügbar.

Lange Rede, kurzer Sinn:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
//Multisampling-Count enumerieren (Quality wird automatisch ermittelt)
for(DWORD dwCount= 2; dwCount < D3D11_MAX_MULTISAMPLE_SAMPLE_COUNT; dwCount++)
{
    //Unterstützung abfragen
    DWORD dwNumLevels;
    pDevice->CheckMultisampleQualityLevels(DXGI_FORMAT_R8G8B8A8_UNORM, dwCount, (UINT*)&dwNumLevels);

    //Eintragen
    if(dwNumLevels > 0)
        pToFill->push_back(dwCount);
}


Warum fang ich nicht bei 0 an, sonder erst bei 2?
Ganz einfach: 0 bedeutete bei D3D9 aus. Bei D3D11 ist es ungültig. 1 bedeutet jetzt aus. Da 1 aber genauso keine QualityLevels besitzt, fang ich erst ab 2 an.

Da man MSAA aber auch abschalten können soll, muss man ein paar kleine Ausnahmen machen:
Bei der Auswahl der MSAA-Counts einfach "OFF" zur Auswahl stellen. Wenn "OFF" gewählt wurde, setzt man später für den SampleCount 1 ein und für das QualityLevel 0.

Die fertige Funktion sollte keine Neuerungen enthalten:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
void ListMultisampling(IDXGIFactory1 * pFactory, DWORD dwAdapter, std::list<DWORD> * pToFill)
{
    //Parameter überprüfen
    if(!pFactory || !pToFill)
        LOG_ERROR_AND_THROW("Invalid parameters!");

    //Liste leeren
    pToFill->clear();

    //Adapter holen
    IDXGIAdapter1 * pAdapter;
    TRY_DX(pFactory->EnumAdapters1(dwAdapter, &pAdapter));

    //Ein Dummy-Device erstellen
    D3D_FEATURE_LEVEL aWantedFeatureLevel[]=
    {
        D3D_FEATURE_LEVEL_11_0,
        D3D_FEATURE_LEVEL_10_1,
        D3D_FEATURE_LEVEL_10_0,
        D3D_FEATURE_LEVEL_9_3,
        D3D_FEATURE_LEVEL_9_2
    };
    D3D_FEATURE_LEVEL CreatedFeatureLevel;
    ID3D11Device * pDevice;
    ID3D11DeviceContext * pDeviceContext;
    TRY_DX(D3D11CreateDevice(pAdapter,
                             D3D_DRIVER_TYPE_UNKNOWN,
                             NULL,
                             0,
                             aWantedFeatureLevel,
                             sizeof(aWantedFeatureLevel) / sizeof(D3D_FEATURE_LEVEL),
                             D3D11_SDK_VERSION,
                             &pDevice,
                             &CreatedFeatureLevel,
                             &pDeviceContext));

    //Multisampling-Count enumerieren (Quality wird automatisch ermittelt)
    for(DWORD dwCount= 2; dwCount < D3D11_MAX_MULTISAMPLE_SAMPLE_COUNT; dwCount++)
    {
        //Unterstützung abfragen
        DWORD dwNumLevels;
        pDevice->CheckMultisampleQualityLevels(DXGI_FORMAT_R8G8B8A8_UNORM, dwCount, (UINT*)&dwNumLevels);

        //Eintragen
        if(dwNumLevels > 0)
            pToFill->push_back(dwCount);
    }

    //Dummy-Device freigeben
    pDeviceContext->Release();
    pDevice->Release();
    pAdapter->Release();

    //Erfolg gehabt?
    if(pToFill->empty())
        LOG_ERROR_AND_THROW("Enumeration failed! List is empty.");
}


Da hätten wir alle!
Um später auch noch das passende QualityLevel wiederzufinden, gibt es eine passende FinderFunktion:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
//Höchstmögliche MultiSampling Qualität finden
DWORD FindMaxMultisamplingQuality(IDXGIFactory1 * pFactory, DWORD dwAdapter, DWORD dwMultiSamplingType)
{
    //Wenn kein Multisampling erwünscht ist,
    ///bleiben mehr garnicht mehr soviele Qualitätsstufen übrig
    if(dwMultiSamplingType == 0 || dwMultiSamplingType == 1)
        return(0);

    //Dummy-Device erstellen: Leider eine ganze Menge Arbeit
    IDXGIAdapter1 * pAdapter;
    TRY_DX(pFactory->EnumAdapters1(0, &pAdapter));
    D3D_FEATURE_LEVEL aWantedFeatureLevel[]=
    {
        D3D_FEATURE_LEVEL_11_0,
        D3D_FEATURE_LEVEL_10_1,
        D3D_FEATURE_LEVEL_10_0,
        D3D_FEATURE_LEVEL_9_3,
        D3D_FEATURE_LEVEL_9_2
    };
    D3D_FEATURE_LEVEL CreatedFeatureLevel;
    ID3D11Device * pDevice;
    ID3D11DeviceContext * pDeviceContext;
    TRY_DX(D3D11CreateDevice(pAdapter,
                             D3D_DRIVER_TYPE_UNKNOWN,
                             NULL,
                             0,
                             aWantedFeatureLevel,
                             sizeof(aWantedFeatureLevel) / sizeof(D3D_FEATURE_LEVEL),
                             D3D11_SDK_VERSION,
                             &pDevice,
                             &CreatedFeatureLevel,
                             &pDeviceContext));

    //Endlich: Qualität abfragen
    DWORD dwNumQualities;
    TRY_DX(pDevice->CheckMultisampleQualityLevels(DXGI_FORMAT_R8G8B8A8_UNORM, dwMultiSamplingType, (UINT*)&dwNumQualities));
    
    //Aufräumen
    pDeviceContext->Release();
    pDevice->Release();
    pAdapter->Release();

    return(dwNumQualities - 1);
}

Sie erzeugt genauso wie ListMultisampling ein DummyDevice. Jetzt werden aber nicht alle SampleCounts probiert, sondern nur der gegebene.

3.5. Anisotropische Filterung

Die anisotropische Filterung ist doch was feines. Man sieht 10% mehr als beim Trilinearen und es flackert.
Aber nur, wenn das Level zu gering ist!
Aber wie viele gibt es denn?
Der Erfahrung nach, könnte man sagen: 2fach, 4fach, 8fach und 16fach.
Ist auch richtig! Denn schon DX10 setzte voraus, dass die GPU anisotropische Filterung bis 16fach (=D3D11_MAX_MAXANISOTROPY) unterstützen muss.
Sinn macht aber nur 2^n fache Filterung.

Deshalb hier die List-Funktion. Sie ähnelt gewaltig der von MSAA, allerdings ist sie auf nichts angewiesen.

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
//Listet alle Anitotropischen Filterungslevel auf.
void ListAnisotropy(std::list<DWORD> * pToFill)
{
    //Liste leeren
    pToFill->clear();

    //Allo von 2 bis <Max> in 2er-Potenzen auflisten
    for(DWORD dwCount= 2; dwCount <= D3D11_MAX_MAXANISOTROPY; dwCount*= 2)
        pToFill->push_back(dwCount);
}


4. Demo zum herunterladen

Ich hab hier mal ein kleines Demo Projekt zusammengestellt, in dem man Adapter und Output wählen kann. Daraufhin bekommt man alle Auflösungen, MSAA Counts und Anitotropy Levels geliefert:

Downloadlink

Weiterhin enthalten sind die Dateien ListVideoSettings.h und ListVideoSettings.cpp, die alle Funktionen enthalten, die ich hier gezeigt habe.

5. Ausblick

Wer bis hierher alles gelesen hat, den beglückwünsche ich schonmal für seine Ausdauer!
Diese Funktionen, die ich vorgestellt habe, legen den Grundstein für alles weitere.
Im nächsten Tutorial werde ich weg von DirectX mich dem Thema "Tabbed Dialogs" wenden. Also Dialogfenster (kennt jeder, kann jeder) mit Tabs (kennt jeder, kann nicht jeder).
Das ganze wird aber abstrahiert: Man ruft die Funktion DoSettingsDialog auf und übergibt ihr eine Liste mit Pages. Sie übernimmt alles weitere.

Und zum Schluss in Teil 3 wird dann so eine Page für D3D11 erstellt. Sie benutzt dann die Funktionen aus diesem Tutorial.


Das wars erstmal!
Wenn Ihr Fehler findet (können auch Rechtschreibfehler sein), Verbesserungsvorschläge habt, etwas nicht versteht, dann lasst es mich wissen.

Bis bald!

Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von »BlazeX« (25.11.2010, 16:00)


2

19.02.2010, 15:36

Wäre echt nice, wenn du die Code als Zip noch bereitstellen könntest. Aber sonst wenns funktioniert klasse Arbeit, habe nur leider momentan wenig Zeit um das alles zu kontrollieren :)

BlazeX

Alter Hase

  • »BlazeX« ist der Autor dieses Themas

Beiträge: 478

Wohnort: DD

Beruf: Maschinenbau-Student

  • Private Nachricht senden

3

21.02.2010, 20:09

Der Code ist jetzt in einem kleinen Demoprojekt enthalten (siehe Tutorial).

Würde sich vieleicht einer der gnädigen Herren dazu bemühen, dieses Tutorial in das Tutorial-Forum zu stecken. :|

EDIT: Danke

BlackSnake

Community-Fossil

Beiträge: 1 549

Beruf: Student

  • Private Nachricht senden

4

23.02.2010, 10:56

ich weiß nicht wie es in em aktuellen sdk ist (feb 10), aber man konnte keinen adapter an die device funktion übergeben. mit dem software modus meine ich ging es, aber nicht im hardware modus, wrap weiß ich nicht mehr^^. normal hat man ja keine zwei adpater und kann dadrauf verzichten, aber wenn man zum beispiel mit nperfhud arbeitet, bekommt man nen software adapater dazu und schon geht das ganze nicht mehr.
aber wie gesagt, evtl ist es in dem feb10 release behoben worden ;)

dot

Supermoderator

Beiträge: 9 757

Wohnort: Graz

  • Private Nachricht senden

5

23.02.2010, 11:26

Zitat von »"BlackSnake"«

ich weiß nicht wie es in em aktuellen sdk ist (feb 10), aber man konnte keinen adapter an die device funktion übergeben.


Also bei mir ging das immer schon ;)

BlazeX

Alter Hase

  • »BlazeX« ist der Autor dieses Themas

Beiträge: 478

Wohnort: DD

Beruf: Maschinenbau-Student

  • Private Nachricht senden

6

23.02.2010, 14:24

Zitat von »"BlackSnake"«

ich weiß nicht wie es in em aktuellen sdk ist (feb 10), aber man konnte keinen adapter an die device funktion übergeben.


Ich hab auch schon davon gehör, aber in der Doku steht dazu folgendes:

Zitat

In Direct3D 11, if you are trying to create a hardware or a software device, set pAdapter != NULL which constrains the other inputs to be:

DriverType must be D3D_DRIVER_TYPE_UNKNOWN
Software must be NULL.

Und genau das mache ich.

Da ich noch nicht mit dem PerfHUD gearbeitet habe, weiß ich nicht, in wie fern das funktioniert. Allerdings hab ich mal testweise noch eine 8500 GT dazugesteckt, und die stand auch als Adapter zur Auswahl. Dazu kam noch der Monitor. Es funktioniert alles einwandfrei.

BlackSnake

Community-Fossil

Beiträge: 1 549

Beruf: Student

  • Private Nachricht senden

7

23.02.2010, 19:57

das auflisten sollte ohne probleme funktionieren, nur das erstellen der device, also die du dann auch nutzt, nicht die, die nur zum überprüfen des machbaren benutzt wird, führte bei mir immer zu problemen. ich habe dann einfach perfhud erstmal weggelassen, da es zu dem zeitpunkt noch nicht win7 unterstützt hat, was jetzt aber vor kurzem nachgeschoben wurde ;)

BlazeX

Alter Hase

  • »BlazeX« ist der Autor dieses Themas

Beiträge: 478

Wohnort: DD

Beruf: Maschinenbau-Student

  • Private Nachricht senden

8

24.02.2010, 22:10

So jetzt ist es raus: NVIDIA PerfHUD unterstützt noch kein D3D11. :shock:

Von daher bleibe ich erstmal bei D3D11 und DXGI 1.1 und gebe keine offizielle Unterstützung von PerfHUD in dieser Demo raus. ;)

@BlackSnake: Das mit dem Erstellen des Devices zum Resourcen erstellen und Rendern mit ausgsuchten Adapter funktioniert wirklich!


Es würde mich freuen, wenn sich überhaupt mal einer die Mühe macht, das Tutorial zu lesen. Sonst war es ja umsonst und ich werde mir nochmal überlegen die anderen beiden Teile zu schreiben.

9

25.02.2010, 18:33

Ich habe heute mal die DX11 Doku zu DXGI durchgestöbert, weil mich die Ausgabe von DXGI_OUTPUT_DESC irgendwie stört ("\\.\Display1"). Man sollte ja eigentlich meinen, dass die Beschreibung ja etwas Sinnvolles wäre wie z.B. bei mir"LG Flatron weiß der Kuckuck". Nun frage ich mich, ob das an den Treibern meines Monitors liegt, oder ob das tatsächlich mit DXGI nicht anders geht? (wohl eher ersteres...)

BlackSnake

Community-Fossil

Beiträge: 1 549

Beruf: Student

  • Private Nachricht senden

10

25.02.2010, 18:34

danke für den hinweis :).
ich habe mir das teils durchgelesen und fand es ganz interssant :). von mir aus, kann es weitere teile geben.

ich beschäftige mich ebenfalls mit dx11 und würde mich wohl zu verfügung stellen, auch ein wenig was zu schreiben, so tutorial like, wenn es dann überhaupt gefordert ist von der community

somit @BlazeX
könnte man sich ja evtl zusammen tun (icq nummer steht ja im profil);)

Werbeanzeige