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

14.06.2010, 19:43

Direct3D11 Settings Dialog - Teil 3 - Video Page

Direct3D11 Settings Dialog - Teil 3 - Video Page

Teil 1: Link
Teil 2: Link
Teil 3: Das isser ;)

Inhalt

1. Einleitung
2. Die VideoPage gestalten
3. Die Programmierung der VideoPage
4. Die Hochzeit von Dialog und VideoPage
5. Demoprojekt
6. Ausblick


1. Einleitung

Jetzt ist er endlich da: Der dritte und letzt Teil dieses Tutorials.
Dieser Teil baut auf den ersten und den zweiten Teil des Tutorials auf, also vorher lesen!
Wer beide schon gelesen hat, dem sollte es eigentlich nicht schwerfallen ohne den dritten Teil auszukommen. Hier ist er trotzdem. :P



2. Die VideoPage gestalten

Eine Art Vorlage für den Dialog ist schon vorhanden. Dieses Bild:

(Link)

Man hat insgesamt 1 Child-Dialog (die VideoPage!) und 7 Steuerelemente für: Adapter, Output, ..., Anisotropische Filterung.
Ich vergebe auch hier laufende Nummern:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//Resource.h
#pragma once

////////////////////////////////////////////////////////////////
//SettingsDialog
//...

//Video-Page
#define IDD_VIDEO_PAGE                          110 //Der Dialog
#define IDC_VIDEO_ADAPTER                       111 //Adapter
#define IDC_VIDEO_OUTPUT                        112 //Output
#define IDC_VIDEO_FULLSCREEN                    113 //Vollbildmodus
#define IDC_VIDEO_RESOLUTION                    114 //Auflösung
#define IDC_VIDEO_VSYNC                         115 //VSync
#define IDC_VIDEO_MULTISAMPLING                 116 //MSAA
#define IDC_VIDEO_ANISOTROPY                    117 //Anisotropische Filterung

Im zweiten Teil habe ich schonmal eine Vorlage für Pages erstellt und hier die Steuerelemente (=Controls) eingefügt. Ich habe mich für 2 verschiedene Control-Typen entschieden: ComboBox und AutoCheckBox.
Eine ComboBox ist eine "ausklappbare Liste". Darin können beliebig viele Einträge stehen, denn mit der Scrollfunktion ist die Liste schnell durchsucht. Von allem, wovon es beliebig viele gibt (wie Adapter, Auflösungen) wird die ComboBox herhalten müssen.
Bei der Wahl ob Vollbildmodus oder nicht, bzw. VSync an oder nicht, hätte ich auch eine ComboBox benutzen können. Allerdings ist mir das für ein simpler Ja/Nein zu blöd gewesen. Es gibt ja nur Ja oder Nein = An oder Aus. Der "Auto" vor "CheckBox" bewirkt, dass sich Windows um das abhaken kümmert, wenn der Benutzer auf die CheckBox klickt.

Fertig sieht die Resource so 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
//Resource.rc
//...

//Video-Page
IDD_VIDEO_PAGE DIALOG 10, 20, 200, 200
STYLE WS_CHILD
FONT 8, "Tahoma"
{
    LTEXT           "Adapter", IDC_STATIC,                  10, 5, 100, 10
    COMBOBOX        IDC_VIDEO_ADAPTER,                      10, 15, 180, 100, CBS_DROPDOWNLIST|WS_VSCROLL

    LTEXT           "Monitor", IDC_STATIC,                  10, 35, 100, 10
    COMBOBOX        IDC_VIDEO_OUTPUT,                       10, 45, 180, 100, CBS_DROPDOWNLIST|WS_VSCROLL

    AUTOCHECKBOX    "Fullscreen", IDC_VIDEO_FULLSCREEN,     10, 65, 60, 10

    LTEXT           "Resolution", IDC_STATIC,               10, 80, 100, 10
    COMBOBOX        IDC_VIDEO_RESOLUTION,                   10, 90, 180, 100, CBS_DROPDOWNLIST|WS_VSCROLL

    AUTOCHECKBOX    "VSync", IDC_VIDEO_VSYNC,               10, 110, 60, 10
    
    LTEXT           "Multisampling", IDC_STATIC,            10, 125, 100, 10
    COMBOBOX        IDC_VIDEO_MULTISAMPLING,                10, 135, 180, 100, CBS_DROPDOWNLIST|WS_VSCROLL

    LTEXT           "Anisotropic Filtering", IDC_STATIC,    10, 155, 100, 10
    COMBOBOX        IDC_VIDEO_ANISOTROPY,                   10, 165, 180, 100, CBS_DROPDOWNLIST|WS_VSCROLL
}



3. Die Programmierung der VideoPage

Auch wenn hier noch Hand angelegt werden muss: Wie speichert die Eingstellungen?
Ich gehe, wie auch in den anderen Teilen, nicht auf das konkrete Speichern und Laden der Einstellungen ein. Aber eine Struktur, die alle Einstellungen enthält, sollte schon sein. Ich nenne sie VideoSettings

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//VideoPage.h
#pragma once
#include<WindowsX.h>
#include"ListVideoSettings.h"
#include"SettingsPageBase.h"
#include"Resource.h"

////////////////////////////////////////////////////////
//Hier drin werden alle Einstellungen gespeichert
struct VideoSettings
{
    DWORD dwAdapter;                //Der Adapter
    DWORD dwOutput;                 //Das Output
    BOOL bFullscreen;               //Vollbild?
    DWORD dwBBSizeX, dwBBSizeY;     //Auflösung (des BackBuffers)
    BOOL bVSync;                    //VSync?
    DWORD dwMultiSamplingType;      //MSAA
    DWORD dwAnisotropy;             //Tex.-Filterung
};


Als nächstes ist die eigentliche VideoPage an der Reihe. Aber wie soll die VideoPage denn überhaupt agieren?
Der Benutzer klickt irgendwo hin, wählt etwas. Bis hierhin macht Windows alles notwendige.
Wenn der Benutzer allerdings das Häkchen einer CheckBox der VideoPage setzt, oder eine andere Auflösung wählt, muss schon etwas unternommen werden. Genau in solchen Momenten wird der geänderte Wert abgefragt und alle davon abhängigen Werte müssen neu aufgelistet werden.
Später klickt der Benutzer dann auf OK bzw. Apply und es wird gespeichert.

Ran an die Sache: Die VideoPage muss von der Klasse SettingsPageBase abgeleitet werden.
Zuerst bekommt die Klasse einige Variablen, darunter die aktuellen Settings, eine IDXGIFactory (von den ListVideo-Funktionen benötigt) und die Handles der Controls:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
////////////////////////////////////////////////////////
//VideoPage für SettingsDialog
class VideoPage : public SettingsPageBase
{
private:
    ///////////////////////////////////////////////////////
    //Variablen
    //Einstellungen
    VideoSettings   CurrentSettings;    //Aktuelle Settings, synchron mit dem Dialog

    //Interfaces fürs Enumerieren
    IDXGIFactory1 * pFactory;           //Schnittstelle zu DXGI, enumerieren der Adapter, Outputs und Resolutions

    //Controls
    HWND hAdapter;
    HWND hOutput;
    HWND hFullscreen;
    HWND hResolution;
    HWND hVSync;
    HWND hMultisampling;
    HWND hAnisotropy;


Konstruktor, Destruktor und die geerbten Methoden sind Pflicht:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public:
    ///////////////////////////////////////////////////////
    //Konstruktor und Destruktor
    VideoPage();
    ~VideoPage();

    ///////////////////////////////////////////////////////
    //Init, Exit und Save
    void Init(HWND hDialog);
    void Exit();
    void Apply();

    ///////////////////////////////////////////////////////
    //Getter
    String GetPageName();
    HMODULE GetDialogResourceModule();
    DWORD GetDialogResourceID();

    ///////////////////////////////////////////////////////
    //CommandHandling
    BOOL OnCommand(WPARAM wParam, LPARAM lParam);

Dazu kommt noch eine private "Clean"-Methode, die alle Variablen nullsetzt.

Außerdem muss auch reagiert werden können, wenn der Benutzer seine Wahl geändert hat. Darum gibt es für jedes änderbare Control eine "OnChanged"-Methode:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
///////////////////////////////////////////////////////
    //Utils für Run
    //User hat die Wahl geändert.
    //Diese Funktionen speichern den geänderten Wert in den CurrentSettings.
    //Die neu verfügbaren (abhängigen) Dinge werden enumeriert.
    //Die Nachricht, dass etwas geändert wurde, wird versandt.
    void OnChangedAdapter();
    void OnChangedOutput();
    void OnChangedFullscreen();
    void OnChangedResolution();
    void OnChangedVSync();
    void OnChangedMultisampling();
    void OnChangedAnisotropy();


In diesen Methoden wird erst der gewählte Wert in den "CurrentSettings" gespeichert und dann die davon abhängigen Werte neu aufgelistet. Wenn der Adapter geändert wird, ist es klar, dass neue Auflösungen zur Verfügung stehen können.
Was von wem anhängig ist, zeigt dieses Diagramm:

(Link)


Die OnCommand-Methode muss also die Aktionen des Benutzers im Auge behalten und gegebenenfalls einer der OnChanged-Methoden aufrufen.
Die AutoCheckBoxes sind nicht das Thema, bei jeder Aktion wechseln sie ihren Zustand. Bei den ComboBoxes muss schonmal nachgefragt werden, ob den überhaupt etwas geändert wurde.
Am Ende entsteht ein typischer Verteiler:

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
///////////////////////////////////////////////////////
//CommandHandling
BOOL VideoPage::OnCommand(WPARAM wParam, LPARAM lParam)
{
    switch(LOWORD(wParam))
    {
    case IDC_VIDEO_ADAPTER:
        if(HIWORD(wParam) != CBN_SELCHANGE) return(FALSE);
        //Adapter wurde geändert
        OnChangedAdapter();
        return(TRUE);

    case IDC_VIDEO_OUTPUT:
        if(HIWORD(wParam) != CBN_SELCHANGE) return(FALSE);
        //Output wurde geändert
        OnChangedOutput();
        return(TRUE);

    case IDC_VIDEO_FULLSCREEN:
        //Fullscreen wurde geändert
        OnChangedFullscreen();
        return(TRUE);

    //Und für die anderen Controls genau so

    default:
        break;
    }

    return(FALSE);
}


Kommen wir gleich mal zu OnChangedAdapter
Die Methode fragt den gewählten Adapter ab, speichert ihn zwischen, lässt die Outputs und MultisamplingCounts neu aufzählen, wählt die ersten verfügbaren und ruft OnChangedOutput und OnChangedMultisampling auf.
Klingt nach viel, darum wird das Enumerieren von Outputs und MultisamplingCounts auch exta-Funktionen überlassen: EnumOuputs und Co.
OnChangedAdapter ist demzufolge garnicht mehr so groß:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
//User hat die Wahl geändert.
void VideoPage::OnChangedAdapter()
{
    //Auswahl holen
    CurrentSettings.dwAdapter= ComboBox_GetCurSel(hAdapter);

    //Der neue Adapter lässt andere Outputs und MSAA zu
    EnumOutputs();
    OnChangedOutput();
    EnumMultisampling();
    OnChangedMultisampling();
}

Die anderen OnChanged-Methoden funktionieren nach genau dem selben Prinzip.

Schauen wir und die Methode EnumOutputs exemplarisch nochmal unter die Lupe:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void VideoPage::EnumOutputs()
{
    //Liste leeren
    ComboBox_ResetContent(hOutput);
    
    //Outputs auflisten
    std::list<Output> Outputs;
    ListOutputs(pFactory, CurrentSettings.dwAdapter, &Outputs);
    for(std::list<Output>::iterator Output= Outputs.begin(); Output != Outputs.end(); Output++)
    {
        //Output eintragen
        ComboBox_InsertString(hOutput, Output->dwID, Output->Name);
    }

    //Alte Auswahl wiederherstellen
    ComboBox_SetCurSel(hOutput, CurrentSettings.dwOutput);
}

Zuerst werden alle bis dato bekannten Outputs in der Liste rausradiert. Nun wird mit der Funktion ListOutputs aus dem ersten Teil erstmal eine neue Liste mit verfügbaren Outputs gefüllt.
Anschließend werden alle Einträge zur ComboBox der Outputs hinzugefügt.

Danach wird versucht die Auswahl "von damals" wieder herzustellen. Das hat den Grund, dass man so einfach das Laden der Einstellungen erleichtert.
Man müsste also in der Init-Methode die Einstellungen in die CurrentSettings laden, den Adapter aus den Einstellungen wählen und OnChangedAdapter auswählen.
Klingt einfach? Ist es auch, denn alles weitere übernehmen rekursiv die anderen OnChanged-Methoden.
Wenn der Adapter also gändert wird, ruft OnChangedAdapter OnChangedOutput und diese Methode wiederum OnChangedResolutions auf.

Interessant ist auch nochmal die EnumResolutions-Methode und OnChangedResolution, denn hier muss geschickt Breite und Höhe lesbar ausgegeben werden.
Dazu werden sogenannte StringStreams verwendet. Sie sind std::cin und std::cout in einem - überaus praktisch.
Lange Rede, kurzer Code:

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
void VideoPage::EnumResolutions()
{
    //Liste leeren
    ComboBox_ResetContent(hResolution);

    //Auflösungen auflisten
    std::list<Resolution> Resolutions;
    ListResolutions(pFactory, CurrentSettings.dwAdapter, CurrentSettings.dwOutput, &Resolutions);
    DWORD dwIndex= 0;
    for(std::list<Resolution>::iterator Resolution= Resolutions.begin(); Resolution != Resolutions.end(); Resolution++)
    {
        //Eintragen
        StringStream Entry;
        Entry<<Resolution->dwX<<" x "<<Resolution->dwY;
        ComboBox_InsertString(hResolution, dwIndex++, Entry.str().c_str());
    }   

    //Alte Auswahl wiederherstellen
    StringStream Choice;
    Choice<<CurrentSettings.dwBBSizeX<<" x "<<CurrentSettings.dwBBSizeY;
    if(ComboBox_SelectString(hResolution, -1, Choice.str().c_str()) == CB_ERR)
        ComboBox_SetCurSel(hResolution, 0);
}

//...

void VideoPage::OnChangedResolution()
{
    //Auswahl holen und interpretieren
    char Buffer[256];
    ComboBox_GetLBText(hResolution, ComboBox_GetCurSel(hResolution), Buffer);

    StringStream Interpreter;
    char cX;
    Interpreter.str(Buffer);
    Interpreter>>CurrentSettings.dwBBSizeX>>cX>>CurrentSettings.dwBBSizeY;
}
[cpp]
Die anderen Methoden sind ähnlich aufgebaut, wenn nicht sogar durch Copy'n'Paste entstanden.

Aber "back to the roots": Wie startet denn eigentlich der Dialog? Na mit der Init-Methode.
Hier wird nicht so viel Wind gemacht:
- Erstmal die SettingsPageBase initialisieren
- Für die ListVideo-Funktionen wird eine IDXGIFactory benötigt, also wird kurzerhand eine erstellt.
- Die Control-Handles müssen noch "initialisiert" werden
- Die Einstellungen, die der Benutzer das letzte mal getroffen hat, müssen geladen werden
- Nach den Einstellungen muss alles weitere enumeriert/selektiert/gecheckt werden.

[cpp]
void VideoPage::Init(HWND hDialog)
{
    SettingsPageBase::Init(hDialog);
    
    //Factory erstellen
    TRY_DX(CreateDXGIFactory1(__uuidof(IDXGIFactory1), (void**)(&pFactory)));
    
    //Einstellungen laden
    //---
    //Das muss hier nachgeholt werden!
    //---   

    //Controls holen
    hAdapter        = GetDlgItem(GetPageWindow(), IDC_VIDEO_ADAPTER);
    hOutput         = GetDlgItem(GetPageWindow(), IDC_VIDEO_OUTPUT);
    hFullscreen     = GetDlgItem(GetPageWindow(), IDC_VIDEO_FULLSCREEN);
    hResolution     = GetDlgItem(GetPageWindow(), IDC_VIDEO_RESOLUTION);
    hVSync          = GetDlgItem(GetPageWindow(), IDC_VIDEO_VSYNC);
    hMultisampling  = GetDlgItem(GetPageWindow(), IDC_VIDEO_MULTISAMPLING);
    hAnisotropy     = GetDlgItem(GetPageWindow(), IDC_VIDEO_ANISOTROPY);

    //Adapter enumerieren, OnChanched senden
    EnumAdapters();
    OnChangedAdapter(); //Dadurch wird automatisch Output, Resolution und MSAA enumeriert und selektiert

    //Übrig bleiben noch: Fullscreen, VSync und Anisotropy
    Button_SetCheck(hFullscreen, CurrentSettings.bFullscreen);
    Button_SetCheck(hVSync, CurrentSettings.bVSync);
    EnumAnisotropy();
}


Die Exit-Methode gibt die IDXGIFactory frei und ruft die Exit-Methode der SettingsPageBase auf.

Das wars!

4. Die Hochzeit von Dialog und VideoPage

Um die VideoPage auch anständig nutzen zu können, muss sie nur in die PageList für den Dialog aufgenommen werden.

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
//Video-Page erstellen
        VideoPage VideoPage;
        PageList Pages;
        Pages.push_back(&VideoPage);

        //Dialog starten
        if(DoSettingsDialog(&Pages))
        {
            //Abgebrochen!
            //...


Mehr ist da nicht zu tun. Außer die Lade und Speicherfunktion!

5. Demoprojekt
Eine Demo gibt's wie immer auch dazu.

Downloadlink


6. Ausblick

Das war das Tutorial, wie man einen Direct3D11-SettingsDialog erstellt. Dazu kommt noch ein Tabbed-Dialog Framework, mit dem einfach individuelle Pages drin sind.
Mittlerweile habe ich auch schon eine Page für ein frei konfigurierbares Steuerungssystem. Das war auch nur: Page designen, Page proggen, fertig.

Ich hoffe, dass wenigstens ein oder zwei Leute etwas aus lernen konnten.
Wenn es noch offene Fragen gibt, bitte melden.

Wer zu den wenigen gehört, die sich das Tutorial zu Gemüte geführt haben, für den sollte es kein Problem sein, dieses System in das eigene Spiel einzubauen.
Und wenn das eigene Spiel OpenGL benutzt, dann wird halt eine OpenGL-Page erstellt!

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


idontknow

unregistriert

2

15.06.2010, 22:16

Wie hast du die GUI erstellt? und wie ddie dami verbundene RC Datei? Nettes Tut werds mir mal aneignen und mit DX9 umsetzen ^^

Ist doch super wenn man ne nette Initialisierung hat :)

BlazeX

Alter Hase

  • »BlazeX« ist der Autor dieses Themas

Beiträge: 478

Wohnort: DD

Beruf: Maschinenbau-Student

  • Private Nachricht senden

3

16.06.2010, 08:55

Die GUI? WinAPI, das ist für solche (kleinen) Fälle besser geeignet, als erst große Geschütze aufzufahren.
Mit den RC-Dateien ist das etwas blöd, man kann sie nicht in der IDE (ich nehm VC++ 10 Express) erstellen. Darum erstelle ich im Projektordner einfach eine "Neue Txt-Datei" und benenne sie in "Resource.rc" um. Dann bloß noch in der IDE die "existierende Datei" hinzufügen, fertig.
Der Resource Compiler der IDE funktioniert aber tadellos. :thumbup:

Schön, wenn es gut ankommt! ^^

Werbeanzeige