DirectX12 CbvHeap

7

Mit directX12 haben sie Heap-Deskriptoren eingeführt. Eine Möglichkeit für uns, die Tabelle für Ressourcen zu beschreiben, die wir an die Shader senden wollten. Ich bin zugegebenermaßen sehr neu in der Computergrafik und habe in directX11 nur ein bisschen gebastelt. Ich habe im Moment nicht mit Instanzen oder komplizierteren Dingen gespielt, also habe ich für jedes Netz, das diese Definitionen hat, ein Objekt.

Microsoft::WRL::ComPtr<ID3D12Resource> mVertexBuffer;
D3D12_VERTEX_BUFFER_VIEW mVertexBufferView;

// Index Buffer
Microsoft::WRL::ComPtr<ID3D12Resource> mIndexBuffer;
D3D12_INDEX_BUFFER_VIEW mIndexBufferView;

// World View Projection Constant Buffer
Microsoft::WRL::ComPtr<ID3D12Resource> mWVPConstantBuffer;
WVPData mWVPData;      
UINT8* mMappedWVPBuffer;

// Directional Light Constant Buffer
Microsoft::WRL::ComPtr<ID3D12Resource> mDirLightConstantBuffer;
DirLightData mDirLightData;
UINT8* mMappedDirLightBuffer;

// CBVHeap
Microsoft::WRL::ComPtr<ID3D12DescriptorHeap> mCbvHeap;
UINT mCbvDescriptorSize;

Ein Vertex-Puffer, ein Index-Puffer, der cbvHeap und zwei konstante Puffer. Eine für die Transformationsmatrizen und eine für die gerichteten Lichtdaten.

Ich war mir nicht sicher, was der cbvHeap (Heap mit konstanter Pufferansicht) wirklich tat. Ich wusste nur, wie ich es für meine Inhalte auf dem Bildschirm verwenden konnte. Also habe ich experimentiert. Ich habe cbvHeap und mCbvDescriptorSize aus dem Netzobjekt genommen und in das Szenenobjekt (in dem das Netz von Netzen enthalten war) eingefügt und dann denselben cbvheap für alle meine Netze verwendet. Dies funktionierte nicht, da es eine einheitliche Farbe über das gesamte Netz ergab. (dh: Für jedes Netz wurde der gleiche konstante Puffer verwendet, wodurch die gleichen Daten für diffuses und Umgebungslicht erhalten wurden.) Was genau hat der Deskriptorhaufen verursacht, der dies verursacht hat? Und stimmen meine Definitionen pro Netz?

Andrew Wilson
quelle

Antworten:

10

In DX12 ist ein Deskriptor ein kleiner Datensatz, im Grunde ein Zeiger, der der GPU mitteilt, wo sich einige Daten befinden, z. B. ein konstanter Puffer. Da jedes Objekt seine eigenen konstanten Pufferdaten mit seinen eigenen Transformationen, Beleuchtungs- / Materialeigenschaften usw. haben wird, muss jedes Objekt auch einen separaten Satz von Deskriptoren haben, um auf seine individuellen Daten zu verweisen.

Es gibt verschiedene Möglichkeiten, Deskriptoren in DX12 einzurichten. Zunächst können Sie einer Befehlsliste Befehle hinzufügen, mit denen Deskriptoren direkt in der Stammtabelle aktualisiert werden. Sie können beispielsweise SetGraphicsRootConstantBufferView()Deskriptoren für die konstanten Puffer eines Objekts einrichten und dann das Objekt zeichnen. Wiederholen Sie dies für mehrere Objekte. Diese Deskriptoren werden in der Befehlsliste gespeichert und in der Reihenfolge angewendet, in der die Befehlsliste auf der GPU ausgeführt wird.

Die andere Möglichkeit besteht darin, Deskriptorhaufen zu verwenden. Ein Deskriptor-Heap ist im Grunde ein Puffer, in dem ein Array von Deskriptoren gespeichert ist. Bei diesem Ansatz werden in der Befehlsliste keine Deskriptoren für Sie gespeichert. Sie müssen sie selbst verwalten. Wenn Sie Deskriptoren überschreiben, bevor die GPU sie verbraucht, erhalten Sie falsche Ergebnisse. Dies klingt wie das, was in Ihrem Programm passiert: Sie überschreiben wahrscheinlich jedes Mal, wenn Sie die Zeichenbefehle eines Objekts auf der CPU ausgeben, denselben Deskriptor im Heap. Wenn die GPU die Befehlsliste ausführt, ist also nur noch der zuletzt geschriebene Deskriptor übrig dort, und dieser Deskriptor wird auf alle Objekte angewendet.

Um dies zu beheben, müssen Sie im Deskriptor-Heap genügend Speicherplatz zuweisen, um alle Deskriptoren zu speichern, die Sie für den Frame verwenden werden. Schreiben Sie alle Deskriptoren der Objekte in den Heap. Richten Sie dann beim Zeichnen jedes Objekts den Shader SetGraphicsRootDescriptorTable()mit einem Versatzgriff auf die Daten des Objekts, der auf die Stelle im Heap zeigt, an der sich die Deskriptoren des Objekts befinden.

Solange Sie nur versuchen, eine einfache App zu lernen und zum Laufen zu bringen, würde ich mich wahrscheinlich so weit wie möglich an den Root-Table-Ansatz halten. Für eine seriösere Hochleistungs-App gibt es wahrscheinlich Optimierungen, die Sie mit Deskriptor-Heaps vornehmen können, z. B. das Zusammenhalten aller Konstanten und Texturen für ein bestimmtes Material, damit Sie sie für viele Objekte wiederverwenden und alle in einem Aufruf binden können. Sie möchten wahrscheinlich auch vermeiden, einen konstanten Puffer- und Deskriptor-Heap pro Objekt zu haben, sondern die Daten vieler Objekte in wenigen großen Puffern zusammenfassen. Dies ist effizienter, wenn Sie viele Objekte haben.

Es ist auch erwähnenswert, dass eine seriöse 3D-App die Befehlslisten mehrerer Frames gleichzeitig im Flug haben möchte, um eine CPU / GPU-Parallelität zu erzielen. Dazu müssen konstante Puffer und Deskriptor-Heaps verlängert werden, um den Wert mehrerer Frames zu speichern von Dateien. Auch dies können Sie zu Lern- / Aufrüstungszwecken beschönigen, indem Sie jeweils nur eine Befehlsliste erstellen, diese senden und darauf warten, dass sie abgeschlossen ist, bevor Sie mit dem nächsten Frame fortfahren.

Nathan Reed
quelle
Das Problem bei beiden Methoden (Festlegen der GPU-Adresse des konstanten Puffers mit SetGraphicsRootConstantBufferView () oder Verwenden sekundärer Suchvorgänge für die Deskriptoren in der Stammtabelle) besteht darin, dass der Anwendungsentwickler eine Strategie implementieren muss, wenn nicht genügend GPU-Adressen zum Rendern aller Objekte verfügbar sind Konstanten Pufferspeicher wiederverwenden. Die Anwendung muss möglicherweise die aktuelle Befehlsliste (Close () und dann Reset ()) schließen und senden. Dies bedeutet, dass Sie den Status (PSO und anderer Status) in eine neue Befehlsliste duplizieren müssen. Wie sollte eine Anwendung konstanten Pufferspeicher in einer Ringpufferstrategie verwenden
Sieht so aus, als hätte SE den Beitrag in der Konvertierung zum Kommentar abgeschnitten. Es wird fortgesetzt mit "... y? In der Vergangenheit haben Treiber festgestellt, dass kein Überschreiben festgelegt wurde, oder den Speicher umbenannt, wenn der vorherige Speicher belegt war. Wie implementieren Sie diese Strategien in DX12?"
Martin Ender