Leistungsoptimierungen (Direct3D 9)
Jeder Entwickler, der Echtzeitanwendungen erstellt, die 3D-Grafiken verwenden, ist besorgt über die Leistungsoptimierung. Dieser Abschnitt enthält Richtlinien zum Erzielen der besten Leistung aus Ihrem Code.
- allgemeine Leistungstipps
- Datenbanken und Culling-
- Batchverarbeitungsgrundtypen
- Beleuchtungstipps
- Texturgröße
- Matrixtransformationen
- Verwenden von dynamischen Texturen
- Verwenden von dynamischen Vertex- und Indexpuffern
- Verwenden von Gittern
- Z-Pufferleistung
Allgemeine Leistungstipps
- Nur löschen, wenn Sie müssen.
- Minimieren Sie Zustandsänderungen, und gruppieren Sie die verbleibenden Zustandsänderungen.
- Verwenden Sie kleinere Texturen, falls möglich.
- Zeichnen Sie Objekte in Ihrer Szene von vorne nach hinten.
- Verwenden Sie Dreiecksstreifen anstelle von Listen und Lüftern. Ordnen Sie Für eine optimale Leistung des Vertexcaches Streifen an, um Dreiecksverknutzungen früher und nicht später wiederzuverwenden.
- Sondereffekte, die einen unverhältnismäßigen Anteil an Systemressourcen erfordern, werden ordnungsgemäß herabgesetzt.
- Testen Sie die Leistung Ihrer Anwendung ständig.
- Minimieren Sie Vertexpufferoptionen.
- Verwenden Sie nach Möglichkeit statische Vertexpuffer.
- Verwenden Sie einen großen statischen Vertexpuffer pro FVF für statische Objekte anstelle eines puffers pro Objekt.
- Wenn Ihre Anwendung zufälligen Zugriff auf den Vertexpuffer im AGP-Speicher benötigt, wählen Sie eine Vertexformatgröße aus, die ein Vielfaches von 32 Bytes ist. Wählen Sie andernfalls das kleinste geeignete Format aus.
- Zeichnen mit indizierten Grundtypen. Dies kann eine effizientere Vertexzwischenspeicherung innerhalb der Hardware ermöglichen.
- Wenn das Tiefenpufferformat einen Schablonenkanal enthält, löschen Sie immer die Tiefen- und Schablonenkanäle gleichzeitig.
- Kombinieren Sie die Shaderanweisung und die Datenausgabe, sofern möglich. Zum Beispiel:
// Rather than doing a multiply and add, and then output the data with // two instructions: mad r2, r1, v0, c0 mov oD0, r2 // Combine both in a single instruction, because this eliminates an // additional register copy. mad oD0, r1, v0, c0
Datenbanken und Culling
Das Erstellen einer zuverlässigen Datenbank der Objekte in Ihrer Welt ist der Schlüssel zur hervorragenden Leistung in Direct3D. Es ist wichtiger als Verbesserungen bei der Rasterung oder Hardware.
Sie sollten die niedrigste Polygonanzahl beibehalten, die Sie möglicherweise verwalten können. Entwerfen Sie von Anfang an eine niedrige Polygonanzahl, indem Sie Low-Polygon-Modelle erstellen. Fügen Sie Polygone hinzu, wenn Sie dies tun können, ohne die Leistung später im Entwicklungsprozess zu beeinträchtigen. Denken Sie daran, dass die schnellsten Polygone die sind, die Sie nicht zeichnen.
Batchverarbeitungsgrundtypen
Um die beste Renderingleistung während der Ausführung zu erzielen, versuchen Sie, mit Grundtypen in Batches zu arbeiten und die Anzahl der Renderzustandsänderungen so gering wie möglich zu halten. Wenn Sie beispielsweise ein Objekt mit zwei Texturen haben, gruppieren Sie die Dreiecke, die die erste Textur verwenden, und folgen Sie diesen mit dem erforderlichen Renderzustand, um die Textur zu ändern. Gruppieren Sie dann alle Dreiecke, die die zweite Textur verwenden. Die einfachste Hardwareunterstützung für Direct3D wird mit Batches von Renderzuständen und Batches von Grundtypen über die Hardwarestraktionsebene (HAL) aufgerufen. Je effektiver die Anweisungen im Batch ausgeführt werden, desto weniger HAL-Aufrufe werden während der Ausführung ausgeführt.
Beleuchtungstipps
Da Leuchten jedem gerenderten Frame eine Kosten pro Scheitelpunkt hinzufügen, können Sie die Leistung erheblich verbessern, indem Sie sorgfältig darauf achten, wie Sie sie in Ihrer Anwendung verwenden. Die meisten der folgenden Tipps werden von der Maxime abgeleitet: "Der schnellste Code ist Code, der nie aufgerufen wird."
- Verwenden Sie so wenige Lichtquellen wie möglich. Um beispielsweise den Gesamtbeleuchtungsgrad zu erhöhen, verwenden Sie das Umgebungslicht, anstatt eine neue Lichtquelle hinzuzufügen.
- Richtungslichter sind effizienter als Punktlichter oder Spotlights. Bei Direktlichtern ist die Richtung des Lichts fest und muss nicht pro Scheitelpunkt berechnet werden.
- Spotlights können effizienter sein als Punktlichter, da der Bereich außerhalb des Lichtkegels schnell berechnet wird. Ob Spotlights effizienter sind oder nicht, hängt davon ab, wie viel Ihrer Szene vom Spotlight beleuchtet wird.
- Verwenden Sie den Bereichsparameter, um ihre Lichter nur auf die Teile der Szene zu beschränken, die Sie erleuchten müssen. Alle Lichttypen gehen relativ früh aus, wenn sie nicht mehr verfügbar sind.
- Glanzlichter verdoppeln fast die Kosten eines Lichts. Verwenden Sie sie nur, wenn Sie dies müssen. Legen Sie den D3DRS_SPECULARENABLE Renderzustand auf 0 fest, den Standardwert, wenn möglich. Beim Definieren von Materialien müssen Sie den Glanzleistungswert auf Null festlegen, um glanzförmige Hervorhebungen für dieses Material zu deaktivieren; Das Festlegen der Glanzfarbe auf 0,0,0 reicht nicht aus.
Texturgröße
Die Texturzuordnungsleistung hängt stark von der Geschwindigkeit des Arbeitsspeichers ab. Es gibt eine Reihe von Möglichkeiten, die Cacheleistung der Texturen Ihrer Anwendung zu maximieren.
- Halten Sie die Texturen klein. Je kleiner die Texturen sind, desto besser ist die Wahrscheinlichkeit, dass sie im sekundären Cache der Haupt-CPU verwaltet werden.
- Ändern Sie die Texturen nicht pro Grundtyp. Versuchen Sie, Polygone in der Reihenfolge der verwendeten Texturen zu gruppieren.
- Verwenden Sie nach Möglichkeit quadratische Texturen. Texturen, deren Abmessungen 256 x 256 sind, sind die schnellsten. Wenn Ihre Anwendung beispielsweise vier 128x128-Texturen verwendet, versuchen Sie, sicherzustellen, dass sie dieselbe Palette verwenden und alle in einer Textur von 256 x 256 platzieren. Mit dieser Technik wird auch die Menge des Texturtauschs reduziert. Natürlich sollten Sie 256 x 256 Texturen nicht verwenden, es sei denn, Ihre Anwendung erfordert so viel Texturierung, da, wie erwähnt, Texturen so klein wie möglich gehalten werden sollten.
Matrixtransformationen
Direct3D verwendet die Welt- und Ansichtsmatrizen, die Sie zum Konfigurieren mehrerer interner Datenstrukturen festgelegt haben. Jedes Mal, wenn Sie eine neue Welt- oder Ansichtsmatrix festlegen, berechnet das System die zugehörigen internen Strukturen neu. Das häufige Festlegen dieser Matrizen – z. B. Tausende von Mal pro Frame – ist rechenaufwändig. Sie können die Anzahl der erforderlichen Berechnungen minimieren, indem Sie Ihre Welt- und Ansichtsmatrizen in eine Weltansichtsmatrix verketten, die Sie als Weltmatrix festlegen, und dann die Ansichtsmatrix auf die Identität festlegen. Bewahren Sie zwischengespeicherte Kopien einzelner Welt- und Ansichtsmatrizen auf, damit Sie die Weltmatrix nach Bedarf ändern, verketten und zurücksetzen können. Aus Gründen der Klarheit in dieser Dokumentation verwenden Direct3D-Beispiele diese Optimierung selten.
Verwenden dynamischer Texturen
Um herauszufinden, ob der Treiber dynamische Texturen unterstützt, überprüfen Sie die D3DCAPS2_DYNAMICTEXTURES Kennzeichnung der D3DCAPS9 Struktur.
Beachten Sie beim Arbeiten mit dynamischen Texturen die folgenden Punkte.
- Sie können nicht verwaltet werden. Beispielsweise kann ihr Pool nicht D3DPOOL_MANAGED werden.
- Dynamische Texturen können gesperrt werden, auch wenn sie in D3DPOOL_DEFAULT erstellt werden.
- D3DLOCK_DISCARD ist eine gültige Sperrkennzeichnung für dynamische Texturen.
Es empfiehlt sich, nur eine dynamische Textur pro Format und möglicherweise pro Größe zu erstellen. Dynamische mipmaps, Cubes und Volumes werden aufgrund des zusätzlichen Aufwands beim Sperren aller Ebenen nicht empfohlen. Bei Mipmaps ist D3DLOCK_DISCARD nur auf der obersten Ebene zulässig. Alle Ebenen werden verworfen, indem nur die oberste Ebene gesperrt wird. Dieses Verhalten ist für Volumes und Cubes identisch. Bei Würfeln sind die oberste Ebene und das Gesicht 0 gesperrt.
Der folgende Pseudocode zeigt ein Beispiel für die Verwendung einer dynamischen Textur.
DrawProceduralTexture(pTex)
{
// pTex should not be very small because overhead of
// calling driver every D3DLOCK_DISCARD will not
// justify the performance gain. Experimentation is encouraged.
pTex->Lock(D3DLOCK_DISCARD);
<Overwrite *entire* texture>
pTex->Unlock();
pDev->SetTexture();
pDev->DrawPrimitive();
}
Verwenden von dynamischen Vertex- und Indexpuffern
Das Sperren eines statischen Vertexpuffers, während der Grafikprozessor den Puffer verwendet, kann erhebliche Leistungseinbußen haben. Der Sperraufruf muss warten, bis der Grafikprozessor vertex- oder Indexdaten aus dem Puffer liest, bevor er zur aufrufenden Anwendung zurückkehren kann, eine erhebliche Verzögerung. Durch das Sperren und anschließendes Rendern aus einem statischen Puffer pro Frame wird auch verhindert, dass der Grafikprozessor Renderbefehle puffert, da sie Befehle fertig stellen muss, bevor der Sperrzeiger zurückgegeben wird. Ohne gepufferte Befehle bleibt der Grafikprozessor im Leerlauf, bis die Anwendung das Ausfüllen des Vertexpuffers oder Indexpuffers abgeschlossen hat und einen Renderingbefehl ausgibt.
Im Idealfall würden sich die Vertex- oder Indexdaten nie ändern, dies ist jedoch nicht immer möglich. Es gibt viele Situationen, in denen die Anwendung Vertex- oder Indexdaten für jeden Frame ändern muss, vielleicht sogar mehrmals pro Frame. In diesen Fällen sollte der Vertex- oder Indexpuffer mit D3DUSAGE_DYNAMIC erstellt werden. Diese Verwendungskennzeichnung bewirkt, dass Direct3D für häufige Sperrvorgänge optimiert wird. D3DUSAGE_DYNAMIC ist nur nützlich, wenn der Puffer häufig gesperrt ist; Daten, die konstant bleiben, sollten in einem statischen Vertex- oder Indexpuffer platziert werden.
Um bei verwendung dynamischer Vertexpuffer eine Leistungsverbesserung zu erhalten, muss die Anwendung IDirect3DVertexBuffer9::Lock oder IDirect3DIndexBuffer9::Lock mit den entsprechenden Flags aufrufen. D3DLOCK_DISCARD gibt an, dass die Anwendung die alten Vertex- oder Indexdaten nicht im Puffer behalten muss. Wenn der Grafikprozessor den Puffer weiterhin verwendet, wenn die Sperre mit D3DLOCK_DISCARD aufgerufen wird, wird anstelle der alten Pufferdaten ein Zeiger auf einen neuen Speicherbereich zurückgegeben. Auf diese Weise kann der Grafikprozessor die alten Daten weiterhin verwenden, während die Anwendung Daten im neuen Puffer platziert. In der Anwendung ist keine zusätzliche Speicherverwaltung erforderlich; Der alte Puffer wird automatisch wiederverwendet oder zerstört, wenn der Grafikprozessor damit fertig ist. Beachten Sie, dass das Sperren eines Puffers mit D3DLOCK_DISCARD immer den gesamten Puffer verwirft, wobei ein Nichtzero-Offset- oder beschränktes Feld keine Informationen in nicht gesperrten Bereichen des Puffers erhalten bleibt.
Es gibt Fälle, in denen die Datenmenge, die die Anwendung pro Sperre speichern muss, klein ist, z. B. das Hinzufügen von vier Scheitelpunkten zum Rendern eines Sprites. D3DLOCK_NOOVERWRITE gibt an, dass die Anwendung daten, die bereits im dynamischen Puffer verwendet werden, nicht überschreibt. Der Sperraufruf gibt einen Zeiger auf die alten Daten zurück, sodass die Anwendung neue Daten in nicht verwendeten Bereichen des Vertex- oder Indexpuffers hinzufügen kann. Die Anwendung sollte keine Scheitelpunkte oder Indizes ändern, die in einem Draw-Vorgang verwendet werden, da sie möglicherweise noch vom Grafikprozessor verwendet werden. Die Anwendung sollte dann D3DLOCK_DISCARD verwenden, nachdem der dynamische Puffer voll ist, um einen neuen Speicherbereich zu erhalten und die alten Vertex- oder Indexdaten nach Abschluss des Grafikprozessors zu verwerfen.
Der asynchrone Abfragemechanismus ist nützlich, um festzustellen, ob Scheitelpunkte noch vom Grafikprozessor verwendet werden. Ausgeben einer Abfrage vom Typ D3DQUERYTYPE_EVENT nach dem letzten DrawPrimitive-Aufruf, der die Scheitelpunkte verwendet. Die Scheitelpunkte werden nicht mehr verwendet, wenn IDirect3DQuery9::GetData- S_OK zurückgibt. Durch das Sperren eines Puffers mit D3DLOCK_DISCARD oder ohne Flags wird immer sichergestellt, dass die Scheitelpunkte ordnungsgemäß mit dem Grafikprozessor synchronisiert werden. Die Verwendung von Sperren ohne Kennzeichnungen führt jedoch zu der zuvor beschriebenen Leistungsstrafe. Andere API-Aufrufe wie IDirect3DDevice9::BeginScene, IDirect3DDevice9::EndSceneund IDirect3DDevice9::P resent garantieren nicht, dass der Grafikprozessor mit Vertices fertig ist.
Im Folgenden finden Sie Möglichkeiten, dynamische Puffer und die richtigen Sperrkennzeichnungen zu verwenden.
// USAGE STYLE 1
// Discard the entire vertex buffer and refill with thousands of vertices.
// Might contain multiple objects and/or require multiple DrawPrimitive
// calls separated by state changes, etc.
// Determine the size of data to be moved into the vertex buffer.
UINT nSizeOfData = nNumberOfVertices * m_nVertexStride;
// Discard and refill the used portion of the vertex buffer.
CONST DWORD dwLockFlags = D3DLOCK_DISCARD;
// Lock the vertex buffer.
BYTE* pBytes;
if( FAILED( m_pVertexBuffer->Lock( 0, 0, &pBytes, dwLockFlags ) ) )
return false;
// Copy the vertices into the vertex buffer.
memcpy( pBytes, pVertices, nSizeOfData );
m_pVertexBuffer->Unlock();
// Render the primitives.
m_pDevice->DrawPrimitive( D3DPT_TRIANGLELIST, 0, nNumberOfVertices/3)
// USAGE STYLE 2
// Reusing one vertex buffer for multiple objects
// Determine the size of data to be moved into the vertex buffer.
UINT nSizeOfData = nNumberOfVertices * m_nVertexStride;
// No overwrite will be used if the vertices can fit into
// the space remaining in the vertex buffer.
DWORD dwLockFlags = D3DLOCK_NOOVERWRITE;
// Check to see if the entire vertex buffer has been used up yet.
if( m_nNextVertexData > m_nSizeOfVB - nSizeOfData )
{
// No space remains. Start over from the beginning
// of the vertex buffer.
dwLockFlags = D3DLOCK_DISCARD;
m_nNextVertexData = 0;
}
// Lock the vertex buffer.
BYTE* pBytes;
if( FAILED( m_pVertexBuffer->Lock( (UINT)m_nNextVertexData, nSizeOfData,
&pBytes, dwLockFlags ) ) )
return false;
// Copy the vertices into the vertex buffer.
memcpy( pBytes, pVertices, nSizeOfData );
m_pVertexBuffer->Unlock();
// Render the primitives.
m_pDevice->DrawPrimitive( D3DPT_TRIANGLELIST,
m_nNextVertexData/m_nVertexStride, nNumberOfVertices/3)
// Advance to the next position in the vertex buffer.
m_nNextVertexData += nSizeOfData;
Verwenden von Gittern
Sie können Gitter optimieren, indem Sie Direct3D-indizierte Dreiecke anstelle von indizierten Dreiecksstreifen verwenden. Die Hardware ermittelt, dass 95 Prozent der aufeinander folgenden Dreiecke tatsächlich Streifen bilden und entsprechend anpassen. Viele Treiber tun dies auch für ältere Hardware.
D3DX-Gitterobjekte können jedes Dreieck oder jedes Gesicht mit einem DWORD gekennzeichnet haben, das als Attribut dieses Gesichts bezeichnet wird. Die Semantik des DWORD ist benutzerdefinierter. Sie werden von D3DX verwendet, um das Gitter in Teilmengen zu klassifizieren. Die Anwendung legt Attribute pro Face mithilfe des ID3DXMesh::LockAttributeBuffer Aufrufs fest. Die ID3DXMesh::Optimize-Methode hat die Möglichkeit, die Gittervertices und Gesichter mithilfe der option D3DXMESHOPT_ATTRSORT zu gruppieren. Wenn dies erfolgt, berechnet das Gitterobjekt eine Attributtabelle, die von der Anwendung abgerufen werden kann, indem ID3DXBaseMesh::GetAttributeTableaufgerufen wird. Dieser Aufruf gibt 0 zurück, wenn das Gitter nicht nach Attributen sortiert ist. Es gibt keine Möglichkeit, eine Attributtabelle für eine Anwendung festzulegen, da sie von der ID3DXMesh::Optimize-Methode generiert wird. Die Attributsortierung ist datensensitiv. Wenn die Anwendung also weiß, dass ein Gitter sortiert ist, muss sie weiterhin ID3DXMesh::Optimize aufrufen, um die Attributtabelle zu generieren.
In den folgenden Themen werden die verschiedenen Attribute eines Gitters beschrieben.
Attribut-ID
Eine Attribut-ID ist ein Wert, der eine Gruppe von Gesichtern einer Attributgruppe zuordnet. Diese ID beschreibt, welche Teilmenge von Gesichtern ID3DXBaseMesh::D rawSubset- zeichnen soll. Attribut-IDs werden für die Gesichter im Attributpuffer angegeben. Die tatsächlichen Werte der Attribut-IDs können alles sein, was in 32 Bit passt, aber es ist üblich, 0 bis n zu verwenden, wobei n die Anzahl der Attribute ist.
Attributpuffer
Der Attributpuffer ist ein Array von DWORDs (ein pro Gesicht), das angibt, in welcher Attributgruppe jedes Gesicht gehört. Dieser Puffer wird beim Erstellen eines Gitters auf Null initialisiert, wird jedoch entweder von den Laderoutinen gefüllt oder muss vom Benutzer ausgefüllt werden, wenn mehrere Attribute mit ID 0 gewünscht werden. Dieser Puffer enthält die Informationen, mit denen das Gitter basierend auf Attributen in ID3DXMesh::Optimizesortiert wird. Wenn keine Attributtabelle vorhanden ist, überprüft ID3DXBaseMesh::D rawSubset diesen Puffer, um die Gesichter des zu zeichnenden Attributs auszuwählen.
Attributtabelle
Die Attributtabelle ist eine Struktur, die dem Gitter gehört und verwaltet wird. Die einzige Möglichkeit zum Generieren ist das Aufrufen ID3DXMesh::Optimize mit aktivierter Attributsortierung oder einer stärkeren Optimierung. Die Attributtabelle wird verwendet, um schnell einen einfachen Draw-Grundtypaufruf an ID3DXBaseMesh::D rawSubset-zu initiieren. Die einzige andere Verwendung besteht darin, dass die voranschreibenden Gitter auch diese Struktur beibehalten, sodass es möglich ist, zu sehen, welche Gesichter und Scheitelpunkte auf der aktuellen Detailebene aktiv sind.
Leistung des Z-Puffers
Anwendungen können die Leistung erhöhen, wenn Sie Z-Puffer und Textur verwenden, indem Sie sicherstellen, dass Szenen von vorne nach hinten gerendert werden. Texturierte z-gepufferte Grundtypen werden auf Scanzeilenbasis mit dem Z-Puffer vorab getestet. Wenn eine Scanlinie durch ein zuvor gerendertes Polygon ausgeblendet ist, lehnt es das System schnell und effizient ab. Die Z-Pufferung kann die Leistung verbessern, aber die Technik ist am nützlichsten, wenn eine Szene mehr als einmal dieselben Pixel zeichnet. Dies ist schwierig, genau zu berechnen, aber Sie können oft eine enge Annäherung vornehmen. Wenn dieselben Pixel weniger als zweimal gezeichnet werden, können Sie die beste Leistung erzielen, indem Sie Z-Puffer deaktivieren und die Szene von hinten nach vorne rendern.
Verwandte Themen