Direct3D 12-Interop
D3D12 kann verwendet werden, um komponentenbestandteilte Anwendungen zu schreiben.
Übersicht über Interop
D3D12 kann sehr leistungsfähig sein und Anwendungen das Schreiben von Grafikcode mit konsolenähnlicher Effizienz ermöglichen, aber nicht jede Anwendung muss das Rad neu erfinden und die gesamte Rendering-Engine von Grund auf neu schreiben. In einigen Fällen hat eine andere Komponente oder Bibliothek dies bereits besser gemacht, oder in anderen Fällen ist die Leistung eines Teils des Codes nicht so wichtig wie seine Richtigkeit und Lesbarkeit.
In diesem Abschnitt werden die folgenden Interop-Techniken behandelt:
- D3D12 und D3D12 auf demselben Gerät
- D3D12 und D3D12 auf verschiedenen Geräten
- D3D12 und beliebige Kombinationen von D3D11, D3D10 oder D2D auf demselben Gerät
- D3D12 und beliebige Kombinationen von 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 eine D3D12-Interoperabilität mit anderen APIs wünschen würde. Einige Beispiele:
- Inkrementelle Portierung: Sie möchten eine gesamte Anwendung von D3D10 oder D3D11 zu D3D12 portieren, während sie in zwischengeschalteten Phasen des Portierungsprozesses funktioniert (um Tests und Debuggen zu ermöglichen).
- Blackboxcode: Möchte einen bestimmten Teil einer Anwendung unverändert lassen, während der Rest des Codes portiert wird. Beispielsweise ist es möglicherweise nicht erforderlich, UI-Elemente eines Spiels zu portieren.
- Unveränderliche Komponenten: Sie müssen Komponenten verwenden, die sich nicht im Besitz der Anwendung befinden, die nicht in das D3D12-Ziel geschrieben werden.
- Eine neue Komponente: Sie möchten nicht die gesamte Anwendung portieren, sondern möchten eine neue Komponente verwenden, die mit D3D12 geschrieben wird.
Es gibt vier Standard Techniken für die Interoperabilität in D3D12:
- Eine App kann eine offene Befehlsliste für eine Komponente bereitstellen, die einige zusätzliche Renderingbefehle für ein bereits gebundenes 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 UI/Text zu einem bereits gebundenen Rü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 eine sauber 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 möglicherweise parallel eine Befehlsliste erstellt, die die App zu einem späteren Zeitpunkt für die Übermittlung verantwortlich ist. Mindestens eine Ressource muss über Komponentengrenzen hinweg bereitgestellt werden. Dieselbe Technik ist 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 ähnelt der Vorgängerversion
ISurfaceQueue
und dem moderneren IDXGIKeyedMutex.
Die Unterschiede zwischen diesen Szenarien sind genau das, was zwischen den Komponentengrenzen geteilt wird. Das Gerät wird als freigegeben angenommen, 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 von ihnen hat ihre eigenen Komplikationen, wenn sie geteilt werden.
Freigeben einer Befehlsliste
Die einfachste Methode der Interoperabilität erfordert, dass nur eine Befehlsliste für einen Teil der Engine freigegeben wird. Nachdem die Renderingvorgänge abgeschlossen sind, geht der Besitz der Befehlsliste auf den Aufrufer zurück. Der Besitz der Befehlsliste kann über den Stapel nachverfolgt werden. Da Befehlslisten SingleThreads sind, gibt es für eine App keine Möglichkeit, mit dieser Technik etwas Einzigartiges oder Innovatives zu tun.
Freigeben einer Befehlswarteschlange
Wahrscheinlich die gebräuchlichste Technik für mehrere Komponenten, die ein Gerät im selben Prozess gemeinsam nutzen.
Wenn die Befehlswarteschlange die Einheit der Freigabe 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 Synchronisierungsgrundtypen
Das erwartete Muster für eine Komponente, die auf ihren eigenen Geräten und/oder Befehlswarteschlangen arbeitet, besteht darin, ein ID3D12Fence - oder freigegebenes Handle zu akzeptieren, und ein UINT64-Paar bei Beginn der Arbeit, auf das sie wartet, und dann ein zweites ID3D12Fence- oder freigegebenes Handle und UINT64-Paar, das signalisiert, wenn alle Arbeiten abgeschlossen sind. Dieses Muster entspricht der aktuellen Implementierung von IDXGIKeyedMutex und dem DWM/DXGI Flip-Modellsynchronisierungsentwurf.
Gemeinsame Nutzung von Ressourcen
Der bei weitem 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, was sich auf das Ressourcenlayout und entweder auf gültige Sätze von Vorgängen oder Leistungsmerkmalen des Zugriffs auf die Ressourcendaten auswirkt.
Es gibt zwei Muster für den Umgang mit dieser Komplikation, 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 im Standardzustand befinden, wenn die Arbeit gestartet wird, und wird wieder in den Standardzustand versetzt, wenn die Arbeit abgeschlossen ist", oder es kann kompliziertere Regeln haben, die die Freigabe eines Tiefenpuffers ermöglichen, ohne zwischengeschaltete Tiefenauflösungen zu erzwingen.
- Der Vertrag kann von der Anwendung zur Laufzeit definiert werden, zu dem Zeitpunkt, zu dem die Ressource über Komponentengrenzen hinweg freigegeben wird. Es besteht aus den gleichen zwei Informationen: dem Zustand, in dem sich die Ressource befindet, wenn die Komponente sie verwendet, und dem Zustand, in dem die Komponente sie nach Abschluss belassen soll.
Auswählen eines Interopmodells
Für die meisten D3D12-Anwendungen ist die Freigabe einer Befehlswarteschlange wahrscheinlich das ideale Modell. Es ermöglicht den vollständigen Besitz der Arbeitserstellung und -übermittlung, ohne den zusätzlichen Arbeitsspeicheraufwand, der durch redundante Warteschlangen entsteht, und ohne die Auswirkungen der Arbeit mit den GPU-Synchronisierungsgrundtypen.
Die Freigabe von Synchronisierungsgrundtypen ist erforderlich, sobald sich die Komponenten mit verschiedenen Warteschlangeneigenschaften befassen müssen, z. B. Typ oder Priorität, oder sobald die Freigabe Prozessgrenzen überschreiten muss.
Das Freigeben oder Erstellen von Befehlslisten wird von Drittanbieterkomponenten nicht häufig verwendet, kann jedoch häufig in Komponenten verwendet werden, die in einer 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 von Interoperationen.
Siehe auch die ID3D12Device::CreateSharedHandle-Methode , mit der Sie Oberflächen zwischen Windows-Grafik-APIs freigeben können.