Direct3D 12-Interop

D3D12 kann verwendet werden, um komponentenisierte Anwendungen zu schreiben.

Übersicht über Interop

D3D12 kann sehr leistungsfähig sein und es Anwendungen ermöglichen, Grafikcode mit konsolenähnlicher Effizienz zu schreiben, aber nicht jede Anwendung muss das Rad neu gestalten und die gesamte Rendering-Engine von Grund auf neu schreiben. In einigen Fällen hat eine andere Komponente oder Bibliothek dies bereits verbessert, oder in anderen Fällen ist die Leistung eines Teils des Codes nicht so wichtig wie ihre Richtigkeit und Lesbarkeit.

In diesem Abschnitt werden die folgenden Interoptechniken behandelt:

  • D3D12 und D3D12 auf demselben Gerät
  • D3D12 und D3D12 auf verschiedenen Geräten
  • D3D12 und eine beliebige Kombination aus D3D11, D3D10 oder D2D auf demselben Gerät
  • D3D12 und eine beliebige Kombination aus D3D11, D3D10 oder D2D auf verschiedenen Geräten
  • D3D12 und GDI oder D3D12 und D3D11 und GDI

Gründe für die Verwendung von Interop

Es gibt mehrere Gründe, warum eine Anwendung D3D12-Interop mit anderen APIs wünschen würde. Einige Beispiele:

  • Inkrementelles Portieren: Sie möchten eine gesamte Anwendung von D3D10 oder D3D11 nach D3D12 portieren, während sie in zwischengeschalteten Phasen des Portierungsprozesses funktioniert (um Tests und Debuggen zu ermöglichen).
  • Blackbox-Code: Sie möchten einen bestimmten Teil einer Anwendung beim Portieren des restlichen Codes gleich lassen. Beispielsweise kann es nicht erforderlich sein, Benutzeroberflächenelemente eines Spiels zu porten.
  • Nicht veränderbare Komponenten: Komponenten müssen verwendet werden, die sich nicht im Besitz der Anwendung befinden und nicht in das Ziel D3D12 geschrieben werden.
  • Eine neue Komponente: Nicht die gesamte Anwendung portieren, sondern eine neue Komponente verwenden, die mit D3D12 geschrieben wurde.

Es gibt vier Haupttechniken für interop in D3D12:

  • Eine App kann eine geöffnete Befehlsliste für eine Komponente bereitstellen, die einige zusätzliche Renderingbefehle in einem bereits gebundenen Renderziel aufzeichnet. Dies entspricht der Bereitstellung eines vorbereiteten Gerätekontexts für eine andere Komponente in D3D11 und eignet sich hervorragend für Dinge wie das Hinzufügen von Benutzeroberfläche/Text zu einem bereits gebundenen Zurückpuffer.
  • Eine App kann eine Befehlswarteschlange für eine Komponente zusammen mit einer gewünschten Zielressource bereitstellen. Dies entspricht der Verwendung von ClearState- oder DeviceContextState-APIs in D3D11, um einen sauberen Gerätekontext für eine andere Komponente bereitzustellen. So funktionieren Komponenten wie D2D.
  • Eine Komponente kann sich für ein Modell entscheiden, bei dem sie eine Befehlsliste erzeugt, möglicherweise parallel, die von der App zu einem späteren Zeitpunkt übermittelt wird. Es muss mindestens eine Ressource über Komponentengrenzen hinweg bereitgestellt werden. Dieses Verfahren ist auch in D3D11 mit verzögerten Kontexten verfügbar, obwohl die Leistung in D3D12 wünschenswerter ist.
  • Jede Komponente verfügt über eigene Warteschlangen und/oder Geräte, und die App und die Komponenten müssen Ressourcen und Synchronisierungsinformationen über Komponentengrenzen hinweg freigeben. Dies ist vergleichbar mit dem Legacy- ISurfaceQueue und dem moderneren IDXGIKeyedMutex.

Die Unterschiede zwischen diesen Szenarien bestehen darin, was genau von den Komponentengrenzen gemeinsam genutzt wird. Es wird davon ausgegangen, dass das Gerät freigegeben wird, aber da es im Grunde zustandslos ist, ist es nicht wirklich relevant. Die wichtigsten Objekte sind die Befehlsliste, die Befehlswarteschlange, die Synchronisierungsobjekte und die Ressourcen. Jede dieser Beiden hat ihre eigenen Schwierigkeiten, wenn sie freigegeben werden.

Freigeben einer Befehlsliste

Die einfachste Methode der Interop erfordert, dass nur eine Befehlsliste mit einem Teil der Engine geteilt wird. Sobald die Renderingvorgänge abgeschlossen sind, geht der Besitz der Befehlsliste an den Aufrufer zurück. Der Besitz der Befehlsliste kann über den Stapel nachverfolgt werden. Da Befehlslisten singlethreading sind, gibt es für eine App keine Möglichkeit, mit dieser Technik etwas Einzigartiges oder Innovatives zu tun.

Freigeben einer Befehlswarteschlange

Wahrscheinlich die gängigste Technik für mehrere Komponenten, die ein Gerät im gleichen Prozess gemeinsam nutzen.

Wenn die Befehlswarteschlange die Freigabeeinheit ist, muss die Komponente aufgerufen werden, um sie darüber zu informieren, dass alle ausstehenden Befehlslisten sofort an die Befehlswarteschlange übermittelt werden müssen (und alle internen Befehlswarteschlangen synchronisiert werden müssen). Dies entspricht der D3D11 Flush-API und ist die einzige Möglichkeit, wie die Anwendung eigene Befehlslisten übermitteln oder Primitive synchronisieren kann.

Freigeben von Synchronisierungsprimitiven

Das erwartete Muster für eine Komponente, die auf ihren eigenen Geräten und/oder Befehlswarteschlangen betrieben wird, besteht darin, ein ID3D12Fence- oder freigegebenes Handle und ein UINT64-Paar zu beginn der Arbeit zu akzeptieren, auf das gewartet wird, und dann ein zweites ID3D12Fence- oder freigegebenes Handle und ein UINT64-Paar, das signalisiert wird, wenn alle Arbeiten abgeschlossen sind. Dieses Muster entspricht der aktuellen Implementierung von IDXGIKeyedMutex und dem DWM/DXGI-Flipmodellsynchronisierungsentwurf.

Gemeinsame Nutzung von Ressourcen

Der mit Abstand komplizierteste Teil des Schreibens einer D3D12-App, die mehrere Komponenten nutzt, ist der Umgang mit den Ressourcen, die über Komponentengrenzen hinweg gemeinsam genutzt werden. Dies ist hauptsächlich auf das Konzept der Ressourcenzustände zurückzuführen. Während einige Aspekte des Ressourcenzustandsentwurfs für die Synchronisierung innerhalb der Befehlsliste vorgesehen sind, haben andere Auswirkungen zwischen Befehlslisten, die sich auf das Ressourcenlayout und entweder auf gültige Vorgänge oder Leistungsmerkmale des Zugriffs auf die Ressourcendaten auswirken.

Es gibt zwei Muster für den Umgang mit dieser Komplizierung, die beide im Wesentlichen einen Vertrag zwischen Komponenten beinhalten.

  • Der Vertrag kann vom Komponentenentwickler definiert und dokumentiert werden. Dies kann so einfach sein wie "Die Ressource muss sich beim Starten der Arbeit im Standardzustand befinden und wird wieder in den Standardzustand versetzt, wenn die Arbeit abgeschlossen ist", oder es könnten kompliziertere Regeln vorhanden sein, um das Freigeben eines Tiefenpuffers zu ermöglichen, ohne zwischengeschaltete Tiefenre auflösungen zu erzwingen.
  • Der Vertrag kann von der Anwendung zur Laufzeit definiert werden, zu dem Zeitpunkt, zu dem die Ressource über Komponentengrenzen hinweg gemeinsam genutzt wird. Sie besteht aus den gleichen beiden Informationen: dem Zustand, in dem sich die Ressource befindet, wenn die Komponente mit der Verwendung beginnt, und dem Zustand, in dem die Komponente sie bei Abschluss belassen sollte.

Auswählen eines Interopmodells

Für die meisten D3D12-Anwendungen ist die Gemeinsame Nutzung einer Befehlswarteschlange wahrscheinlich das ideale Modell. Sie ermöglicht den vollständigen Besitz der Arbeitserstellung und -übermittlung, ohne den zusätzlichen Arbeitsspeicheraufwand durch redundante Warteschlangen und ohne die auswirkungen auf den Umgang mit gpu-Synchronisierungsprimitiven.

Die Freigabe von Synchronisierungsprimitiven ist erforderlich, sobald die Komponenten verschiedene Warteschlangeneigenschaften verarbeiten müssen, z. B. Typ oder Priorität, oder wenn die Freigabe Prozessgrenzen umfassen muss.

Das Freigeben oder Erstellen von Befehlslisten wird von Drittanbieterkomponenten nicht häufig extern verwendet, kann aber häufig in Komponenten verwendet werden, die für eine Spiel-Engine intern sind.

Interop-APIs

Das Thema Direct3D 11 on 12 führt Sie durch die Verwendung eines Großteils der API-Oberfläche im Zusammenhang mit den in diesem Thema beschriebenen Arten der Interoperabilität.

Siehe auch die ID3D12Device::CreateSharedHandle-Methode, mit der Sie Oberflächen zwischen Windows Grafik-APIs freigeben können.