Qt goes to DirectX9 SDK-Tutorial Teil 2

Hier können eigene Projekte, die mit Qt in Beziehung stehen vorgestellt werden.
Antworten
Querdenker
Beiträge: 99
Registriert: 1. Dezember 2005 17:44
Wohnort: Karlsruhe

Qt goes to DirectX9 SDK-Tutorial Teil 2

Beitrag von Querdenker »

DIRECTX9 SDK-TUTORIAL
TEIL 2

Inhalt:

Titel: Rendering Vertices
->Step 1: Defining a Custom Vertex Type
->Step 2: Setting Up the Vertex Buffer
->Step 3: Rendering the Display

Hi,

wie schon gesagt, hat der Code-Teil des 1. Teils des SDK schon in den 2. Teil vorgegriffen. Hier nun genauer, wie sich MS die Render() Funktion (bei uns eben eine Methode) vorstellt, die von paintEvent angepeitscht wird (es ist wirklich so!). Nach MS sollte die Render() Funktion immer der gleichen Struktur folgen die da lautet:

Code: Alles auswählen

void QDirectX9::Render(){

      //Zuerst den Backbuffer/ViewPort löschen
     IDevice->Clear();

      // Begin the scene.
     IDevice->BeginScene();
         //und Action
     IDevice->EndScene();

     //It's Show-Time
     IDevice->Present();
};
IDevice ist ein Object, eine Instanz von: IDirect3DDevice9. Die COM-Class, die für DirectX Graphics zuständig ist ;) Sie wurde in Initialize() aus IDirectX->CreateDevice() instanziiert (siehe Teil 1).
Die praktische Umsetzung sah so aus:

Code: Alles auswählen

void QDirectX9::Render(){
  if(IDevice == NULL) return;
  
   IDevice->Clear( 0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(100,100,0), 1.0f, 0 );
   IDevice->BeginScene();
        IDevice->SetStreamSource( 0, IVBuffer, 0, sizeof(CUSTOMVERTEX) );
        IDevice->SetFVF( D3DFVF_CUSTOMVERTEX );
        IDevice->DrawPrimitive( D3DPT_TRIANGLELIST , 0, 1 );
   IDevice->EndScene();
   IDevice->Present( NULL, NULL, NULL, NULL );
	
};
Der Code konnte fast 1:1 aus der SDK entnommen und eingesetzt werden und also

-> Clear()
-> BeginScene()
....
-> EndScene()
-> Present()

immer gleich :idea:

->Step 1: Defining a Custom Vertex Type

Im QDirectX9.h Headerfile findet sich unmittelbar nach den Includes eine
Strukturdefinition und ein Makro:

Code: Alles auswählen

struct CUSTOMVERTEX{
    FLOAT x, y, z, rhw; 
    DWORD color;       
};

#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZRHW|D3DFVF_DIFFUSE)
Vorab: Die Datentypen sind alle in der d3d9types.h definiert. Aber nicht diese Struktur: CUSTOMVERTEX. Aber auch nicht das Makro, das eigentlich nur zwei Flags verodert und unter dem Namen: D3DFVF_CUSTOMVERTEX einsetzen wird.

Zu den Datentypen eine Bemerkung: Ich bin der Meinung, dass man speziell für DirectX keine Qt-Typen nehmen sollte, sondern nur die, die MS in der d3d9types.h vordefiniert hat. DirectX wird sowieso eine eigene Welt beschreiben, die sich in die Qt-Umgebung ohne Problem integrieren lässt. Von daher sehe ich keine Probleme Qt-Objekte weiterhin mit Qt-Datentypen und DirectX-Objekte eben mit DirectX-eigenen Typen zu beschreiben. Man bleibt sowieso auf einer win Plattform, da der Einsatz von DirectX plattformabhängig macht (leider).

Zur Vertex-Struktur: X, Y, Z klar :lol: Das sind Raumkoordinatenpunkte, die den Vertex eindeutig im Raum positionieren. Kein Problem, aber FLOAT?! :shock: Der Workaround will nicht beschreiben, wie DirectX funktioniert. Das macht das Tutorial des SDK. Mich interessiert: Wie lässt sich das in Qt integrieren. Eigentlich wollte ich nichts beschreiben, aber hier habe ich definitiv danach gesucht, warum FLOATS? Liegen vielleicht zwischen Pixel 12 und 13 auf der X-Ebene noch 100 oder gar 1000 weitere Points aus der Nachkommastelle oder nur 10? Das Tutorial hat diese Frage nicht geklärt.
Die nächste Auffälligkeit an dem Strukt war der Member: RHW. Niergendwo eine Erklärung, was der Member übernehmen soll, außer: Es ist ein FLOAT. Die Antwort lag tief verborgen im Glossar des SDK und beschreibt RHW:

RHW Transformed vertices in homogeneous space. The (x,y,z) data has already been divided by w (multiplied by the reciprocal of w).

Ich gebe es ganz ehrlich zu: Nach dem Lesen dieser Erklärung bin ich genau so schlau wie vorher.
Für Qt nehme ich hin: Die Struktur muss so sein.

Unter dem Begriff: custom flexible vertex format (FVF) ist es also unter
DirectX möglich, beliebige Formate zu creieren, die einen Vertex beschreiben.

->Step 2: Setting Up the Vertex Buffer

Nachdem man sich auf eine Beschreibung für einen Raumpunkt geeinigt hat, so kann man mit diesen Punkten eine Figur bauen. Hierzu werden die einzelnen Vertex in ein Strukturarray zusammen gefasst:

Code: Alles auswählen

CUSTOMVERTEX vertices[] =
{
    { 150.0f,  50.0f, 0.5f, 1.0f, 0xffff0000, }, // x, y, z, rhw, color
    { 250.0f, 250.0f, 0.5f, 1.0f, 0xff00ff00, },
    {  50.0f, 250.0f, 0.5f, 1.0f, 0xff00ffff, },
};
Das Strukturarray vertices[] nimmt drei definierte Punkte auf, die ein Dreieck im Device beschreiben. Der Code von MS ist sehr komprimiert ,aber sehr übersichtlich.

Der nächste Schritt ist der, dass im Device (Graphikkarte) Speicher allokiert werden muss, der die von uns definierten vertices aufnehmen kann: Den "VertexBuffer". Dazu hat IDevice eine Methode: CreateVertexBuffer IDevice->CreateVertexBuffer();

Code: Alles auswählen

 //Zeichnen ...
  CUSTOMVERTEX vertices[] = {
        { 150.0f,  50.0f, 0.5f, 1.0f, 0xffff0000, }, // x, y, z, rhw, color
        { 250.0f, 250.0f, 0.5f, 1.0f, 0xff00ff00, },
        {  50.0f, 250.0f, 0.5f, 1.0f, 0xff00ffff, }, };
  IDirect3DVertexBuffer9 *IVBuffer;
  if( FAILED(IDevice->CreateVertexBuffer( 3*sizeof(CUSTOMVERTEX),
                                                  0, D3DFVF_CUSTOMVERTEX,
                                                  D3DPOOL_DEFAULT, &IVBuffer, NULL ) ) ){
        return;
   }
Die Addresse dieses Speichers erhalte ich in IVBuffer. Die Variable IVBuffer ist aber so definiert:

IDirect3DVertexBuffer9 *IVBuffer;

Jepp ;) Es ist ein Interface, man bekommt eine Instanz dieser Klasse. Die Default-Eigenschaft der Klasse ist den Offset zum Graphikspeicher zu präsentieren. Das Objekt hat aber nur drei Methoden: Lock/Unlock sowie
eine Beschreibung des Speichers. Also nichts, was gerade vom Hocker haut.

Was aber vom Hocker haut (zumindest Leute, die zum ersten Mal überhaupt selbst Hand an DirectX legen, so wie ich) ist der Satz: Speicher in der Graphikkarte wird allokiert und potzblitz wird schnell klar, was das bescheidene IDevice wirklich präsentiert: Es ist die Graphikkarte! bzw. ein Interface, dass den direkten Hardwarezugriff darauf erlaubt.

Also in diesem Memory der Hardware haben wir Speicher angefordert, der in der Lage ist, drei Vertex aufzunehmen, die in der Struktur vertices vereinigt sind - gesagt aber: Nur die Größe. Die Daten sind längst nicht in diesem Speicher, auf den die Hardware aber zugreift, wenn sie etwas tun soll. AHA! Die Hardware langt also niemals dahin, wo vertices gespeichert ist (geht auch nicht, da Windows das niemals zulassen würde). Daraus folgt: Die Daten müssen von dem einen Speicher in den anderen Speicher geschaufelt werden - wohlan!

Es passiert, indem der Graphikspeicher erstmal gesperrt wird:
IVBuffer->Lock()
Dieser Akt gibt eine Art Key zurück, mittels dem man Zugriff auf den Speicher bekommt, der wird hier in: Void *pVertices abgelegt und stellt eine Addresse dar (Pointer auf diesen Speicher).

Dann folgt eine banale C-Funktion die den Speicher kopiert:

memcpy( pVertices, vertices, sizeof(vertices) );

Anschließend wird die Sicherung wieder aufgehoben mit:

IVBuffer->Unlock();

Bevor jetzt ein anderer Neuling in der Materie her geht und in Euphorie darüber ausbricht direkt an die Graphikkarte langen zu können, ein kleiner Nachhall:
Woher weiß die Graphiks-CPU für welchen Typ von Vertex wir uns entschieden haben? Der Graphik-Ship bearbeitet ja diesen Speicher. Und schon erkennt man, transparent zwar, aber die Bedeutung von dem Hardware Abstaktions Layer (HAL). Das wird von CreateVertexBuffer vorbereitet, die entsprechenden Hebelchen also umgelegt und erst dann erfolgt der Transfer - aber in überwachtem Rahmen. Eine grobe Vereinfachung eines recht komplexen Szenario.

Mit dem Transfer der Daten vom Arbeitsspeicher in den Graphikspeicher ist dieser Teil abgeschlossen.

Dieser Teil a Block:

Code: Alles auswählen

if(IDevice == NULL) return;
  
  //Zeichnen ...
  CUSTOMVERTEX vertices[] = {
        { 150.0f,  50.0f, 0.5f, 1.0f, 0xffff0000, }, // x, y, z, rhw, color
        { 250.0f, 250.0f, 0.5f, 1.0f, 0xff00ff00, },
        {  50.0f, 250.0f, 0.5f, 1.0f, 0xff00ffff, }, };
  IDirect3DVertexBuffer9 *IVBuffer;
  if( FAILED(IDevice->CreateVertexBuffer( 3*sizeof(CUSTOMVERTEX),
                                                  0, D3DFVF_CUSTOMVERTEX,
                                                  D3DPOOL_DEFAULT, &IVBuffer, NULL ) ) ){
        return;
   }
   VOID* pVertices;
   if( FAILED( IVBuffer->Lock( 0, sizeof(vertices), (void**)&pVertices, 0 ) ) )
        return;
   memcpy( pVertices, vertices, sizeof(vertices) );
   IVBuffer->Unlock();

->Step 3: Rendering the Display

SetStreamSource() wird also der Graphiksengine mitteilen, wo wir unsere Graphikdaten hingeschaufelt haben und wie groß dieser Speicher ist.

SetFVF( D3DFVF_CUSTOMVERTEX ); Erklärt die Struktur, wie sich
der Speicher zusammen setzt

DrawPrimitive( D3DPT_TRIANGLELIST , 0, 1 ); sagt der Graphiksengine, dass es eine primitive Figur aus den Graphikdaten zeichnen soll.

und das war's.

Ich hoffe, dass Einige hiermit den Zugang zu DirectX finden können und
nun mit Qt in der Lage sein werden, die SDK-Tuts durchzuarbeiten und
wertvolle Erkenntnisse zur Verwirklichung eigener DirectX-Apps ziehen können.

Have fun ...
Q ... ;)
e Grüssle au
Q... ;)
Antworten