• Keine Ergebnisse gefunden

Kontakt und Updates

2.7 Vertex- und Index-Buffer

2.7.2 Der Vertex-Buffer im Detail

2.7.2.1 Erzeugen eines Vertex-Buffers

Die Schnittstelle für einen Vertex-Buffer heißt IDirect3DVertexBuffer9. Um eine solche zu er-zeugen, verwendet man die Methode CreateVertexBuffer von IDirect3DDevice9. Es ist ein Kin-derspiel, damit einen Vertex-Buffer zu erstellen.

Tabelle 2.29 Die Parameter der Methode IDirect3DDevice9::CreateVertexBuffer

Parameter Beschreibung

UINT Length Größe des Vertex-Buffers in Bytes

DWORD Usage Verwendungszweck des Vertex-Buffers (siehe unten)

DWORD FVF Flexibles Vertexformat für die Vertizes, die in diesem Vertex-Buffer ge-speichert werden sollen (zum Beispiel D3DFVF_XYZ | D3DFVF_DIFFUSE) D3DPOOL Pool Speicherklasse für den Vertex-Buffer. Üblicherweise verwendet man

hier D3DPOOL_MANAGED oder D3DPOOL_DEFAULT. IDirect3DVertexBuffer**

ppVertexBuffer

Adresse eines PDIRECT3DVERTEXBUFFER9-Zeigers, den die Methode ausfüllt

HANDLE* pSharedHandle Dieser Parameter ist unwichtig, und wir werden immer NULL angeben.

D3DUSAGE-Möglichkeiten für Vertex-Buffer

Im Zusammenhang mit Texturen haben Sie bereits einige Bezeichner kennen gelernt, die mit

D3DUSAGE_“ beginnen und den Verwendungszweck einer Textur beschreiben, wie zum Bei-spiel D3DUSAGE_DYNAMIC. Bei Vertex-Buffern gibt es noch mehr Möglichkeiten als bei Texturen.

Beachten Sie, dass Sie die unten aufgeführten Möglichkeiten auch kombinieren können!

Tabelle 2.30 D3DUSAGE-Werte für Vertex-Buffer

Verwendungszweck Beschreibung

0 Erstellt einen ganz gewöhnlichen statischen Vertex-Buffer.

D3DUSAGE_DYNAMIC Geben Sie dieses Flag an, um einen dynamischen Vertex-Buffer erstellen zu lassen. Die Speicherklasse sollte dann D3DPOOL_DEFAULT sein.

Verwendungszweck Beschreibung

D3DUSAGE_POINTS Spezifizieren Sie dieses Flag, um Direct3D zu sagen, dass Sie den Vertex-Buffer später nur verwenden werden, um Punktprimitiven zu zeichnen (D3DPT_POINTLIST).

D3DUSAGE_WRITEONLY Die Anwendung wird nur in den Vertex-Buffer hinein schreiben und nicht aus ihm lesen. Dieses Flag kann die Performance erhöhen, da der Vertex-Buffer dadurch eventuell in einem besseren Speicherbereich angelegt werden kann.

Dynamische Vertex-Buffer

Dynamische Vertex-Buffer lohnen vor allem dann, wenn ihr Inhalt wirklich sehr oft verändert wird (zum Beispiel einmal pro Bild). Ein statischer Vertex-Buffer ist für so hohe Schreib-zugriffsraten nicht unbedingt geeignet. Bei der Arbeit mit einem dynamischen Vertex-Buffer müssen Sie einige Dinge beachten, auf die später noch hingewiesen wird, Sie können dann zum Beispiel nicht die Speicherklasse D3DPOOL_MANAGED verwenden.

Datenabfrage beim Vertex-Buffer

Wenn es später noch einmal nötig ist, nur anhand der IDirect3DVertexBuffer9-Schnittstelle ge-nauere Informationen über den Vertex-Buffer zu erhalten – kein Problem: Die Methode

GetDesc der Vertex-Buffer-Schnittstelle füllt eine Struktur namens D3DVERTEXBUFFER_DESC aus.

Sie besitzt Elemente namens Format, Type, Usage, Pool, Size und FVF, in denen alle Informatio-nen gespeichert sind.

unsigned int GetVertexBufferSize(PDIRECT3DVERTEXBUFFER9 pVertexBuffer) {

D3DVERTEXBUFFER_DESC Desc; // Beschreibung des Vertex-Buffers // Beschreibung abfragen

pVertexBuffer->GetDesc(&Desc);

// Größe des Vertex-Buffers liefern.

// Dazu verwenden wir die gerade abgefrage Struktur.

return Desc.Size;

}

Listing 2.76 Eine Funktion zum Abfragen der Größe eines Vertex-Buffers

2.7.2.2 Füllen des Vertex-Buffers

Kümmern wir uns nun um das Füllen eines Vertex-Buffers, damit könnte man zum Beispiel ein 3D-Modell hineinladen. Da sich Direct3D-Ressourcen, zu denen Vertex-Buffer auch gehö-ren, oft in einem für die Anwendung unsichtbaren Teil des Speichers (zum Beispiel im Gra-fikkartenspeicher) befinden, ist es unmöglich, die Daten „einfach so“ und ohne Vorwarnung zu verändern oder zu lesen. Das Sperren der Ressource ist zuerst nötig. Direct3D liefert der Anwendung dann einen Zeiger auf eine Kopie der Ressource, die im Systemspeicher liegt.

Diese Kopie verändern wir dann, und beim Entsperren sorgt ein Datentransfer dafür, dass die von uns gemachten Änderungen auf die „echte“ Ressource übertragen werden.

Der Sperrvorgang

Der Sperrvorgang wird mit der Methode IDirect3DVertexBuffer9::Lock eingeleitet. Ihr über-gibt man genauere Informationen darüber, welchen Teil man lesen/schreiben möchte und wie das genau passieren wird.

Tabelle 2.31 Die Parameter der Methode IDirect3DVertexBuffer9::Lock

Parameter Beschreibung

UINT OffsetToLock Beschreibt die Stelle in Bytes, ab welcher der Vertex-Buffer gesperrt werden soll. 0 sperrt den Vertex-Buffer von Anfang an.

UINT SizeToLock Anzahl der zu sperrenden Bytes. Geben Sie 0 an (in dem Fall ebenfalls für den vorherigen Parameter), um den gesamten Vertex-Buffer zu sperren.

void** ppData Adresse eines Zeigers, den die Methode nach dem Sperren so ausfüllt, dass er auf den gesperrten Speicherbereich zeigt. Dort führen wir die Veränderungen oder Lesevorgänge durch.

DWORD Flags Flags, welche die Art des Sperrens festlegen (siehe unten)

Die Art des Sperrens

Ein Sperrvorgang kann unter Umständen sehr zu Lasten der Performance gehen, wenn er schlecht gewählt ist. Zuerst ist es wichtig, nur den Teil zu sperren, der auch wirklich verändert wird (falls das bekannt ist). Weiterhin können wir Direct3D schon vor dem Sperren sagen, was mit dem Speicher geschehen wird, so dass auch hier noch die eine oder andere Optimierung stattfinden kann. Die „D3DLOCK_“-Flags (vierter Parameter der Lock-Methode) geben genauere Auskunft.

Tabelle 2.32 Die verschiedenen Sperr-Flags

Sperr-Flag Beschreibung

0 Normaler Sperrmodus

D3DLOCK_NOSYSLOCK Dieses Flag erlaubt dem System, andere Aufgaben zu erledigen, während die Ressource gesperrt ist. Im Falle eines Absturzes könnte man es so vielleicht noch schaffen, das Programm zu beenden. Dies beeinträchtigt jedoch die Performance leicht.

D3DLOCK_READONLY Damit versprechen wir Direct3D, nur aus dem Speicher zu lesen und ihn nicht zu verändern. Die Hardware kann dann sofort mit dem Rendern weiterma-chen (während wir mit dem Speicher arbeiten), weil der Rücktransfer der neuen Daten ja entfällt – alles bleibt so, wie es war. Dies wirkt sich positiv auf die Performance aus. Nicht möglich, wenn der Vertex-Buffer mit D3DUSAGE_WRITEONLY erstellt wurde!

D3DLOCK_NOOVERWRITE Dieses Flag gibt an, dass keine Daten aus dem Vertex-Buffer überschrieben werden, was die Performance ebenfalls stark erhöht.

D3DLOCK_DISCARD Dadurch versichern wir, dass wir den gesamten gesperrten Speicherbereich komplett neu füllen werden, was Direct3D einige Optimierungen ermöglicht.

Die beiden letzten Flags sind nur für dynamische Vertex-Buffer gültig (solche, die mit

D3DUSAGE_DYNAMIC generiert wurden), und man sollte nach Möglichkeit auch von ihnen Gebrauch machen, da sie die angenehme Eigenschaft haben, den ganzen Vorgang stark zu

be-schleunigen. Bei statischen (nicht dynamischen) Vertex-Buffern empfiehlt sich meistens das Flag D3DLOCK_NOSYSLOCK und unter Umständen D3DLOCK_READONLY. Vergessen Sie nicht, dass es auch gültig ist, einfach nur 0 anzugeben!

Datenmanipulation

Nach erfolgreichem Sperren haben wir, was wir wollten: einen Zeiger auf den Speicherbereich mit den Daten der Ressource (in unserem Fall: mit den Daten des Vertex-Buffers). Diesen Zeiger könnten wir nun aus praktischen Gründen in einen Zeiger auf eine SVertex-Struktur umwandeln. Dann sind einfache Befehle wie pVertices[129].vPosition = tbVector3(1.0f, 0.0f, 0.0f) alles, was wir brauchen, um den Vertex-Buffer schließlich zu füllen.

Das Entsperren nicht vergessen!

Wer vergisst zu entsperren, begeht einen großen Fehler! Direct3D muss nämlich, während ei-ne Ressource gesperrt ist, auf Sparflamme laufen – wenn die gesperrte Ressource dann zum Zeichnen benötigt wird, heißt es: warten, warten und nochmals warten, bis die Ressource end-lich entsperrt wird. Zu jedem Aufruf der Lock-Methode gehört ein entsprechender Aufruf der (parameterlosen) Unlock-Methode!

2.7.2.3 Den Inhalt zeichnen

Bisher haben wir unsere Geometrie mit der Methode DrawPrimitiveUP direkt aus dem System-speicher gezeichnet. Nun ist es so weit, dass die Vertizes aus dem wesentlich schnelleren Ver-tex-Buffer kommen. Dazu verwenden wir nun die Methode DrawPrimitive (ohne das „UP“). Im Prinzip ist sie identisch mit der UP-Variante, allerdings entfallen hier ein paar Angaben. Wir brauchen lediglich den Primitiventyp (Dreieckslisten, verbundene Dreiecke und so weiter), den Startvertex und die Anzahl der Primitiven anzugeben. Direct3D weiß sofort, woher es die Vertexdaten nehmen soll: Es benutzt dazu den Datenstrom 0 (mehr dazu gleich).

Tabelle 2.33 Die Parameter der Methode IDirect3DDevice9::DrawPrimitve

Parameter Beschreibung

D3DPRIMITIVETYPE PrimitiveType Typ der zu zeichnenden Primitiven (D3DPT_...)

UINT StartVertex Bezeichnet den Vertex, bei dem mit dem Zeichnen angefangen werden soll (0: erster Vertex)

UINT PrimitiveCount Anzahl der zu zeichnenden Primitiven

Datenströme

Einen Datenstrom kann man sich wie einen Fluss vorstellen, der Daten mit sich trägt, genauer gesagt: Vertexdaten. Ein Datenstrom könnte zum Beispiel die Positionsangaben der Vertizes beinhalten, ein anderer die Texturkoordinaten und noch ein anderer die Farbangaben. Wenn es dann zum Zeichnen kommt, fließen all diese Flüsse zusammen, und Direct3D verbindet sie zu vollständigen Vertizes. Für uns ist erst einmal nur ein Datenstrom wichtig: der Datenstrom 0 (das ist der erste). Wenn nicht anders angegeben, benutzt Direct3D diesen Datenstrom, um die Vertizes für die Methode DrawPrimitive abzufragen. Die Vertizes werden in diesem Daten-strom auch vollständig gespeichert, nicht etwa teilweise. Deren Zerlegung ist ein fortgeschrit-tenes Thema.

Ein Datenstrom kann seine Quelle entweder im Systemspeicher (bei DrawPrimitiveUP ist das der Fall, erinnern Sie sich an den Parameter namens pVertexStreamZeroData?) oder in einem

Vertex-Buffer haben. Bevor wir nun also DrawPrimitive aufrufen, setzen wir unseren Vertex-Buffer als Quelle für den Datenstrom 0. Dafür ist IDirect3DDevice9::SetStreamSource

zuständig.

Tabelle 2.34 Die Parameter der Methode IDirect3DDevice9::SetStreamSource Parameter Beschreibung

UINT StreamNumber Die Nummer des Datenstroms, dessen Quelle wir festlegen IDirect3DVertexBuffer9*

pStreamData

Dies ist der Vertex-Buffer, der als neue Quelle für den Datenstrom akti-viert werden soll. Eine Systemspeicheradresse als Quelle manuell zu setzen ist nicht möglich, das kann nur DrawPrimitiveUP.

UINT OffsetInBytes Startwert in Bytes, erst ab dieser Stelle im Vertex-Buffer werden Daten transportiert. Normalerweise gibt man hier 0 an.

UINT Stride Die Anzahl der Bytes von einem Datenpaket zum nächsten. Ein Daten-paket kann entweder ein vollständiger Vertex (dann geben wir einfach die Vertexgröße an) oder aber auch nur ein Teil davon sein.

Vorsicht! Die SetStreamSource-Methode erhöht den Referenzzähler der angegebenen IDirect3DVertexBuffer9-Schnittstelle! Er wird erst wieder verringert, wenn der Vertex-Buffer durch einen anderen abgelöst oder NULL übergeben wurde.