Wie gehe ich beim Rendern von Code am besten mit ID3D11InputLayout um?

7

Ich suche nach einer eleganten Möglichkeit, Eingabelayouts in meinem directx11-Code zu verarbeiten.

Das Problem, das ich habe, ist, dass ich eine Effektklasse und eine Elementklasse habe. Die Effektklasse kapselt Shader und ähnliche Einstellungen, und die Element-Klasse enthält etwas, das gezeichnet werden kann (3D-Modell, Landschaft usw.).

Mein Zeichencode legt die Geräte-Shader usw. mit dem angegebenen Effekt fest und ruft dann die Zeichenfunktion des Elements auf, um die darin enthaltene tatsächliche Geometrie zu zeichnen.

Das Problem ist folgendes: Ich muss irgendwo ein D3D11InputLayout erstellen. Dies gehört wirklich zur Element-Klasse, da es für den Rest des Systems keine Sache ist, wie dieses Element sein Scheitelpunkt-Layout darstellt. Um das Objekt zu erstellen, benötigt die API jedoch den Vertex-Shader-Bytecode für den Vertex-Shader, der zum Zeichnen des Objekts verwendet wird. In directx9 war es einfach, es gab keine Abhängigkeit, so dass mein Element seine eigenen Eingabelayoutstrukturen enthalten und diese festlegen konnte, ohne dass der Effekt beteiligt war.

Das Element sollte jedoch eigentlich nichts über den Effekt wissen müssen, mit dem es gezeichnet wird. Es handelt sich lediglich um Rendereinstellungen, und das Element ist dazu da, Geometrie bereitzustellen.

Ich weiß also nicht genau, wo ich speichern soll und wie ich das InputLayout für jeden Draw-Aufruf auswählen soll. Ich meine, ich habe etwas zum Laufen gebracht, aber es scheint sehr hässlich.

Dies führt dazu, dass ich entweder etwas Offensichtliches übersehen habe oder dass mein Design, alle Rendereinstellungen in einem Effekt, die Geometrie in einem Element und einen Drittanbieter, der alles zeichnet, zu haben, einfach fehlerhaft ist.

Sie fragen sich nur, wie jemand anderes seine Eingabelayouts in directx11 auf elegante Weise handhabt?

JohnB
quelle

Antworten:

3

Ich gehe damit um, dass ich eine VertexFormat.h-Datei habe, in der ich eine Reihe von Eingabelayouts verschiedener Typen (VertexPositionNormal, VertexNormalUV ...) habe, die zur einfachen Erstellung / Zerstörung in Strukturen eingeschlossen sind. Es funktioniert ziemlich gut, obwohl es hässlich IMO ist. Es ist sehr einfach, der Datei ein neues Scheitelpunktformat hinzuzufügen, wenn Sie einen Shader haben, der ein Layout verwendet, das derzeit nicht unterstützt wird. Dann habe ich einen VertexFormatManager, der diese Layouts erstellt / zerstört, wenn ich sie brauche. Wenn ich also einen erstellen muss, gebe ich den Shader-Byte-Code und eine Vertexformat-ID an den Manager weiter. Er erstellt ihn für mich und speichert ihn in einem Pool, sodass Sie immer nur einen von jedem Layout haben. Dann müssen Sie Ihre Geometrie erstellen. Zu diesem Zweck habe ich eine Create-Methode für meine Geometrieklasse, die eine ID für ein Scheitelpunktformat verwendet. In der Erstellungsfunktion wird der Vertex-Format-Manager mit der angegebenen ID abgefragt. Der Manager gibt eine Ganzzahl zurück, die als Bitfeld dient, wobei für jeden Elementtyp, den das Eingabelayout enthält, ein Flag gesetzt ist. Also könnte ich so etwas haben:

int bitfield = VertexFormatManager::QueryFormat( formatID );
if (bitfield & VertexFormat::NORMAL)
{
    // do somethings
}
if (bitfield & VertexFormat::UV)
{
    // do somethings
}
...

Jedes Eingabe-Layout-Element wird in einem separaten ID3D11-Puffer erstellt. Dadurch kann ich ein Netz für jede Art von Scheitelpunktformat erstellen. Angenommen, Sie möchten einen Würfel mit einer Textur rendern. Er benötigt Scheitelpunktpositionen und UV-Koordinaten. Sie geben ihm die VertexPositionUV-Format-ID (als Beispiel) und die Abfrage gibt ein Bitfeld zurück, das gesetzte Bits für die POSITION- und UV-Flags enthält. Dadurch werden zwei ID3D11-Puffer erstellt: einer für die Positionen und einer für die UV-Koordinaten. Anschließend befestigen Sie beide vor dem Zeichnen an der Pipeline. Dies hat den Vorteil, dass Sie, wenn Sie beispielsweise Schattenkarten erstellen möchten, ein Netz nur mit dem festgelegten Scheitelpunktpositionspuffer rendern können.

Ich bin mir nicht sicher, ob vieles davon sehr klar ist, ich bin auf der Arbeit und werde versuchen, später am Abend richtig zu schreiben. In der Zwischenzeit habe ich diese Frage vor einiger Zeit gestellt und diese Antwort erhalten . Es ist gut, aber ich finde, dass die Art und Weise, wie ich es jetzt mache, sinnvoller ist, insbesondere im Fall von DX11.

dotminic
quelle
Das macht sehr viel Sinn, danke für die umfassende Antwort ... Sie verwenden also mehrere Vertex-Puffer pro Zeichenbefehl?
JohnB
Ja, ich verwende On Buffer für jedes Element (Position, Normalen, UV ...), es ist abhängig von der Art des Renderings nützlich und kann ziemlich flexibel sein. Dies ist sicherlich nicht der einzige Weg, aber es funktioniert meiner Meinung nach gut für die meisten Rendering-Routinen. Ich habe hier ein wenig mehr Details und Pseudocode hinzugefügt: pastebin.com/XfqS6N7g Hoffe, es hilft.
Dotminic
6

Vielen Dank für die Antworten, sie sind sehr hilfreich. Ich werde eine Antwort auf meine eigene Frage hinzufügen, weil ich von dort aus ein bisschen weitergezogen bin.

Am Ende wurde mir klar, dass es vielleicht ein Fehler war, die Shader vollständig von den ziehbaren Gegenständen trennen zu wollen. Sie sind im "realen Leben" grundsätzlich miteinander verbunden, so dass es vielleicht kein Problem ist, dass sie im Design sind.

Zum Beispiel funktioniert ein Wasser-Shader nur mit einem Element, das Wasser ziehen möchte. Nur so können die benötigten Daten bereitgestellt werden. Ein Shader, der animierte Modelle zeichnet, funktioniert nur mit einem animierten Modellelement. Ich kann mich nicht plötzlich für einen Wassershader mit einem animierten Modell entscheiden. Es kann einfach nicht funktionieren.

Der Versuch, eine abstrakte Shader-Klasse zu erstellen, die jeden Shader laden und mit jedem zeichnbaren Element arbeiten kann, ist ein Fehler. Es spiegelt nicht wider, wie sie in der Realität verwendet werden können.

Also habe ich stattdessen Shader-Klassen AnimatedModelShader, WaterShader, LandscapeShader erstellt. Jede dieser Optionen verfügt möglicherweise über Optionen, die dazu führen, dass unterschiedliche physische Shader-Dateien geladen werden. Sie verfügen jedoch notwendigerweise immer über dieselbe grundlegende Benutzeroberfläche, da ein Landscape-Shader immer dieselbe Art von Dateneingabe benötigt.

Der Shader ist nun für die Erstellung seines eigenen Eingabelayouts verantwortlich und teilt dem verwendeten Element mit, wie seine Scheitelpunkte zu gestalten sind, indem ein öffentliches typedef namens vertexType verwendet wird, das das Element verwendet, wenn es diesen Shader verwenden möchte.

Überhaupt nicht mein ursprüngliches Design, aber wie sich herausstellte, war der Wunsch, die beiden Konzepte zu trennen, sowieso nicht sehr nützlich.

JohnB
quelle
3

Ich mache etwas Ähnliches wie dotminic - ich habe eine Routine, an die ich ein Array von D3D11_INPUT_ELEMENT_DESC übergebe, das dann einen gefälschten Vertex-Shader mit einer passenden Eingabesignatur erstellt, kompiliert, ein Layout daraus erstellt und schließlich den gefälschten Shader freigibt.

Ja, es ist hässlich, und ja, es bedeutet, eine Milbe vorsichtiger sein zu müssen, als ich es sonst tun würde, wenn ich es richtig machen würde, aber es sind Kompromisse, die ich derzeit im Austausch für eine sauberere Trennung akzeptieren möchte.

Maximus Minimus
quelle
Ich möchte nur darauf hinweisen, dass ich mich jetzt von diesem Ansatz entfernt habe und Eingangslayouts und Vertex-Shader enger miteinander verbunden habe, um sie immer zusammen zu erstellen. Der Ansatz, den ich hier gebe, ist wahrscheinlich immer noch in Ordnung, wenn Sie wirklich möchten, dass sie entkoppelt werden.
Maximus Minimus
2

Sieht für mich so aus, als müssten Sie sich wirklich keine Sorgen machen. Nachdem ich mir die MSDN-Dokumentation für CreateInputLayout angesehen hatte, stieß ich auf Folgendes:

Sobald ein Eingabe-Layout-Objekt aus einer Shader-Signatur erstellt wurde, kann das Eingabe-Layout-Objekt mit jedem anderen Shader mit identischer Eingabesignatur (einschließlich Semantik) wiederverwendet werden . Dies kann die Erstellung von Eingabe-Layout-Objekten vereinfachen, wenn Sie mit vielen Shadern mit identischen Eingaben arbeiten. (Bingo) Wenn ein Datentyp in der Eingabe-Layout-Deklaration nicht mit dem Datentyp in einer Shader-Eingabe-Signatur übereinstimmt, generiert CreateInputLayout während der Kompilierung eine Warnung. Die Warnung soll lediglich darauf hinweisen, dass die Daten beim Lesen aus einem Register möglicherweise neu interpretiert werden. Sie können diese Warnung entweder ignorieren (wenn eine Neuinterpretation beabsichtigt ist) oder die Datentypen in beiden Deklarationen übereinstimmen lassen, um die Warnung zu beseitigen.

http://msdn.microsoft.com/en-gb/library/windows/desktop/ff476512(v=vs.85).aspx

Dies macht deutlich, dass Sie Ihre Eingabelayouts fast genauso behandeln sollten wie Ihre alten DX9-Vertex-Deklarationen. Wenn Sie beim ersten Erstellen einen Shader-Bytecode angeben, kann DX11 diese nur validieren. Und anscheinend ist das Schlimmste, was passieren wird, wenn Sie diesen Schritt vermeiden oder fehlschlagen, eine Laufzeitwarnung, die Sie ignorieren können.

DX11 "erstellt also keine Abhängigkeit" zwischen Ihren Eingabelayouts und Ihren Shadern ... es bietet eine kostenlose Validierung! : D.

Richard Copperwaite
quelle
CreateInputLayoutkann auch verwendet werden, um einen Shader anhand eines vorhandenen Eingabelayouts zu validieren, sodass Sie alle Shader überprüfen können, die ihn verwenden würden - was ich zumindest in Debug-Builds aus Sicherheitsgründen empfehlen würde.
Nathan Reed
Hmm, als ich dies versuchte, stellte ich fest, dass das Bereitstellen eines vorhandenen Eingabelayouts als Parameter nur dazu führen würde, dass der alte Zeiger überschrieben wird, was zu einem Speicherverlust führt. Schade, dass es keine separate 'Validate'-Funktion gibt.
Richard Copperwaite
Entschuldigung, mit "einem vorhandenen Eingabelayout" habe ich wirklich das Array von D3D11_INPUT_ELEMENT_DESCs gemeint, mit dem es erstellt wurde. Dies kann anhand des Bytecodes eines anderen Shaders überprüft werden, ohne dass ein neues Eingabe-Layout-Objekt erstellt werden muss.
Nathan Reed