Direct3D 12-Renderdurchläufe
Das Renderdurchläufe-Feature ist neu für Windows 10, Version 1809 (10.0; Build 17763) und führt das Konzept eines Direct3D 12-Renderdurchlaufs ein. Ein Renderdurchlauf besteht aus einer Teilmenge der Befehle, die Sie in einer Befehlsliste aufzeichnen.
Um zu deklarieren, wo jeder Renderdurchlauf beginnt und endet, schachteln Sie die Befehle, die zu diesem gehören, in Aufrufen von ID3D12GraphicsCommandList4::BeginRenderPass und EndRenderPass. Folglich enthält jede Befehlsliste null, einen oder mehrere Renderdurchläufe.
Szenarien
Renderdurchläufe können die Leistung Ihres Renderers verbessern, wenn er unter anderem auf Tile-Based Deferred Rendering (TBDR) basiert. Genauer gesagt hilft das Verfahren Ihrem Renderer, die GPU-Effizienz zu verbessern, indem der Arbeitsspeicherdatenverkehr zum bzw. vom Off-Chip-Speicher reduziert wird, indem es Ihrer Anwendung ermöglicht wird, Anforderungen an die Sortierung von Ressourcenrendering und Datenabhängigkeiten besser zu identifizieren.
Ein Anzeigetreiber, der ausdrücklich geschrieben wurde, um die Funktion "Renderdurchläufe" zu nutzen, liefert die besten Ergebnisse. Renderdurchläufe-APIs können jedoch auch auf bereits vorhandenen Treibern ausgeführt werden (jedoch nicht unbedingt mit Leistungsverbesserungen).
Dies sind die Szenarien, in denen Renderdurchläufe als Wert dienen.
Ermöglichen Sie es Ihrer Anwendung, unnötige Auslastungen/Speicher von Ressourcen aus dem bzw. in den Hauptspeicher in einer tbdr-Architektur (Tile-Based Deferred Rendering) zu vermeiden.
Einer der Wertversprechen von Renderdurchläufen ist, dass sie ihnen einen zentralen Ort zur Verfügung stellt, um die Datenabhängigkeiten Ihrer Anwendung für eine Reihe von Renderingvorgängen anzugeben. Diese Datenabhängigkeiten ermöglichen es dem Anzeigetreiber, diese Daten zur Bindungs-/Barrierezeit zu überprüfen und Anweisungen zur Minimierung von Ressourcenladevorgängen/-speichern aus dem/in den Hauptspeicher auszugeben.
Ermöglichen Sie Es Ihrer TBDR-Architektur, ressourcen im On-Chip-Cache über Renderdurchläufe hinweg (auch in separaten Befehlslisten) zu speichern.
Hinweis
Insbesondere ist dieses Szenario auf die Fälle beschränkt, in denen Sie über mehrere Befehlslisten hinweg in dieselben Renderziele schreiben.
Ein gängiges Renderingmuster besteht darin, dass Ihre Anwendung in mehreren Befehlslisten seriell auf denselben Renderzielen rendert, obwohl die Renderingbefehle parallel generiert werden. Durch die Verwendung von Renderdurchläufen in diesem Szenario können diese Durchläufe so kombiniert werden (da die Anwendung weiß, dass das Rendering in der unmittelbar nachfolgenden Befehlsliste fortgesetzt wird), sodass der Anzeigetreiber eine Leerung in den Hauptspeicher an Befehlslistengrenzen vermeiden kann.
Zuständigkeiten Ihrer Anwendung
Selbst mit dem Renderdurchlauf-Feature übernehmen weder die Direct3D 12-Runtime noch der Anzeigetreiber die Verantwortung dafür, Möglichkeiten zur Neuordnung/Vermeidung von Auslastungen und Speichern zu ableiten. Um das Renderdurchläufe-Feature ordnungsgemäß zu nutzen, hat Ihre Anwendung diese Zuständigkeiten.
- Identifizieren Sie die Daten-/Sortierabhängigkeiten für ihre Vorgänge ordnungsgemäß.
- Ordnen Sie die Übermittlungen so an, dass Leerungen minimiert werden (also minimieren Sie die Verwendung von _PRESERVE Flags).
- Verwenden Sie ressourcenbarrieren ordnungsgemäß, und verfolgen Sie den Ressourcenstatus.
- Vermeiden Sie nicht benötigte Kopien/Löschen. Um diese zu identifizieren, können Sie die automatisierten Leistungswarnungen aus dem PIX auf Windows Toolverwenden.
Verwenden der Renderdurchlauffunktion
Was ist ein Renderdurchlauf?
Ein Renderdurchlauf wird von diesen Elementen definiert.
- Eine Reihe von Ausgabebindungen, die für die Dauer des Renderdurchlaufs festgelegt sind. Diese Bindungen gelten für eine oder mehrere Renderzielsichten (RTVs) und/oder für eine Tiefenschablonenansicht (DSV).
- Eine Liste von GPU-Vorgängen, die auf diesen Satz von Ausgabebindungen ausgerichtet sind.
- Metadaten, die die Last-/Speicherabhängigkeiten für alle Ausgabebindungen beschreiben, auf die der Renderdurchlauf abzielt.
Deklarieren Der Ausgabebindungen
Zu Beginn eines Renderdurchlaufs deklarieren Sie Bindungen an Ihre Renderziele und/oder an Ihren Tiefen-/Schablonenpuffer. Es ist optional, an Renderziele zu binden, und es ist optional, an einen Tiefen-/Schablonenpuffer zu binden. Sie müssen jedoch an mindestens eine der beiden Bindungen binden, und im folgenden Codebeispiel binden wir an beide.
Sie deklarieren diese Bindungen in einem Aufruf von ID3D12GraphicsCommandList4::BeginRenderPass.
void render_passes(::ID3D12GraphicsCommandList4 * pIGCL4,
D3D12_CPU_DESCRIPTOR_HANDLE const& rtvCPUDescriptorHandle,
D3D12_CPU_DESCRIPTOR_HANDLE const& dsvCPUDescriptorHandle)
{
const float clearColor4[]{ 0.f, 0.f, 0.f, 0.f };
CD3DX12_CLEAR_VALUE clearValue{ DXGI_FORMAT_R32G32B32_FLOAT, clearColor4 };
D3D12_RENDER_PASS_BEGINNING_ACCESS renderPassBeginningAccessClear{ D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_CLEAR, { clearValue } };
D3D12_RENDER_PASS_ENDING_ACCESS renderPassEndingAccessPreserve{ D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_PRESERVE, {} };
D3D12_RENDER_PASS_RENDER_TARGET_DESC renderPassRenderTargetDesc{ rtvCPUDescriptorHandle, renderPassBeginningAccessClear, renderPassEndingAccessPreserve };
D3D12_RENDER_PASS_BEGINNING_ACCESS renderPassBeginningAccessNoAccess{ D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_NO_ACCESS, {} };
D3D12_RENDER_PASS_ENDING_ACCESS renderPassEndingAccessNoAccess{ D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_NO_ACCESS, {} };
D3D12_RENDER_PASS_DEPTH_STENCIL_DESC renderPassDepthStencilDesc{ dsvCPUDescriptorHandle, renderPassBeginningAccessNoAccess, renderPassBeginningAccessNoAccess, renderPassEndingAccessNoAccess, renderPassEndingAccessNoAccess };
pIGCL4->BeginRenderPass(1, &renderPassRenderTargetDesc, &renderPassDepthStencilDesc, D3D12_RENDER_PASS_FLAG_NONE);
// Record command list.
pIGCL4->EndRenderPass();
// Begin/End further render passes and then execute the command list(s).
}
Sie legen das erste Feld der D3D12_RENDER_PASS_RENDER_TARGET_DESC Struktur auf das CPU-Deskriptorhandle fest, das mindestens einer Renderzielansicht (RTVs) entspricht. Ebenso enthält D3D12_RENDER_PASS_DEPTH_STENCIL_DESC das CPU-Deskriptorhandle, das einer Tiefenschablonenansicht (DSV) entspricht. Diese CPU-Deskriptorhandles sind die gleichen, die Sie andernfalls an ID3D12GraphicsCommandList::OMSetRenderTargetsübergeben würden. Und genau wie bei OMSetRenderTargets werden die CPU-Deskriptoren zum Zeitpunkt des Aufrufs von BeginRenderPass von ihren jeweiligen Heaps (CPU-Deskriptor) angedockt.
RtVs und DSV werden nicht an den Renderdurchlauf geerbt. Stattdessen müssen sie festgelegt werden. RtVs und DSV werden auch nicht in BeginRenderPass deklariert und an die Befehlsliste weitergegeben. Stattdessen befinden sie sich nach dem Renderdurchlauf in einem nicht definierten Zustand.
Rendern von Durchläufen und Workloads
Renderdurchläufe können nicht geschachtelt werden, und sie dürfen nicht über mehrere Befehlslisten gerendert werden (sie müssen beginnen und enden, während sie in einer einzigen Befehlsliste aufgezeichnet werden). Optimierungen, die die effiziente Generierung von Renderdurchläufen mit mehreren Threads ermöglichen, werden im Abschnitt Renderdurchlaufflagsweiter unten erläutert.
Ein Schreibvorgang, den Sie innerhalb eines Renderdurchlaufs durchführen, ist für Das Lesen aus bis zu einem nachfolgenden Renderdurchlauf nicht gültig. Dies schließt einige Arten von Barrieren innerhalb des Renderdurchlaufs — aus, z. B. das Barriering von RENDER_TARGET zu SHADER_RESOURCE auf dem derzeit gebundenen Renderziel. Weitere Informationen finden Sie weiter unten im Abschnitt Rendern von Durchläufen und Ressourcenbarrieren.
Die einzige Ausnahme der gerade erwähnten Schreib-/Leseeinschränkung umfasst die impliziten Lesezugriffe, die im Rahmen von Tiefentests und Renderzielblending erfolgen. Daher sind diese APIs innerhalb eines Renderdurchlaufs nicht zulässig (die Kernruntime entfernt die Befehlsliste, wenn einer von ihnen während der Aufzeichnung aufgerufen wird).
- ID3D12GraphicsCommandList1::AtomicCopyBufferUINT
- ID3D12GraphicsCommandList1::AtomicCopyBufferUINT64
- ID3D12GraphicsCommandList4::BeginRenderPass
- ID3D12GraphicsCommandList::ClearDepthStencilView
- ID3D12GraphicsCommandList::ClearRenderTargetView
- ID3D12GraphicsCommandList::ClearState
- ID3D12GraphicsCommandList::ClearUnorderedAccessViewFloat
- ID3D12GraphicsCommandList::ClearUnorderedAccessViewUint
- ID3D12GraphicsCommandList::CopyBufferRegion
- ID3D12GraphicsCommandList::CopyResource
- ID3D12GraphicsCommandList::CopyTextureRegion
- ID3D12GraphicsCommandList::CopyTiles
- ID3D12GraphicsCommandList::D iscardResource
- ID3D12GraphicsCommandList::D ispatch
- ID3D12GraphicsCommandList::OMSetRenderTargets
- ID3D12GraphicsCommandList::ResolveQueryData
- ID3D12GraphicsCommandList::ResolveSubresource
- ID3D12GraphicsCommandList1::ResolveSubresourceRegion
- ID3D12GraphicsCommandList3::SetProtectedResourceSession
Rendern von Durchläufen und Ressourcenbarrieren
Sie dürfen einen Schreibvorgang, der innerhalb desselben Renderdurchlaufs aufgetreten ist, nicht lesen oder nutzen. Bestimmte Barrieren entsprechen dieser Einschränkung nicht, z. B. von D3D12_RESOURCE_STATE_RENDER_TARGET bis * _SHADER_RESOURCE auf dem derzeit gebundenen Renderziel (und die Debugebene führt zu einem Fehler in diesem Effekt). Dieselbe Barriere auf einem Renderziel, das außerhalb des aktuellen Renderdurchlaufs geschrieben wurde, ist jedoch konform, da die Schreibvorgänge vor dem Start des aktuellen Renderdurchlaufs abgeschlossen werden. Sie können davon profitieren, dass Sie bestimmte Optimierungen kennen, die ein Anzeigetreiber in dieser Hinsicht vornehmen kann. Bei einer konformen Workload kann ein Anzeigetreiber alle Barrieren, die in Ihrem Renderdurchlauf auftreten, an den Anfang des Renderdurchlaufs verschieben. Dort können sie zusammengeknüpft werden (und beeinträchtigen keine Tiling-/Binningvorgänge). Dies ist eine gültige Optimierung, vorausgesetzt, dass alle Schreibvorgänge abgeschlossen sind, bevor der aktuelle Renderdurchlauf gestartet wird.
Hier sehen Sie ein vollständigeres Beispiel für die Treiberoptimierung, bei dem davon ausgegangen wird, dass Sie über eine Rendering-Engine verfügen, die über einen Ressourcenbindungsentwurf vor Direct3D im 12-Stil verfügt, der bei — Bedarf Barrieren basierend auf der Bindung von Ressourcen darstellt. Beim Schreiben in eine ungeordnete Zugriffsansicht (UAV) gegen Ende eines Frames (für die Nutzung im folgenden Frame) kann es vorkommen, dass die Engine die Ressource am Ende des Frames im D3D12_RESOURCE_STATE_UNORDERED_ACCESS Zustand belässt. Wenn die Engine im folgenden Frame die Ressource als Shaderressourcenansicht (Shader Resource View, SRV) bindet, stellt sie fest, dass sich die Ressource nicht im richtigen Zustand befindet, und es wird eine Barriere von D3D12_RESOURCE_STATE_UNORDERED_ACCESS zu D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE ausgegeben. Wenn diese Barriere innerhalb des Renderdurchlaufs auftritt, ist der Anzeigetreiber berechtigt, davon auszugehen, dass alle Schreibvorgänge bereits außerhalb dieses aktuellen Renderdurchlaufs erfolgt sind. Daher kann der Anzeigetreiber die Barriere bis zum Start des Renderdurchlaufs verschieben (und hier an der Stelle, an der die Optimierung stattfindet). Auch hier ist dies gültig, solange Ihr Code der in diesem Abschnitt und der letzten beschriebenen Schreib-/Leseeinschränkung entspricht.
Dies sind Beispiele für konforme Barrieren.
- D3D12_RESOURCE_STATE_UNORDERED_ACCESS, um zu D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT.
- D3D12_RESOURCE_STATE_COPY_DEST, um zu * _SHADER_RESOURCE.
Dies sind Beispiele für nicht konforme Barrieren.
- D3D12_RESOURCE_STATE_RENDER_TARGET zu einem beliebigen Lesezustand auf derzeit gebundenen RTVs/DSVs.
- D3D12_RESOURCE_STATE_DEPTH_WRITE zu einem beliebigen Lesezustand auf derzeit gebundenen RTVs/DSVs.
- Alle Aliasingbarrieren.
- UAV-Barrieren (Unordered Access View).
Ressourcenzugriffsdeklaration
Zur BeginRenderPass-Zeit sowie zum Deklarieren aller Ressourcen, die als RTVs und/oder DSV innerhalb dieses Durchlaufs dienen, müssen Sie auch deren Start- und Endzugriffsmerkmale angeben. Wie Sie im Obigen Codebeispiel im Abschnitt Deklarieren Ihrer Ausgabebindungen sehen können, geschieht dies mit den D3D12_RENDER_PASS_RENDER_TARGET_DESC und D3D12_RENDER_PASS_DEPTH_STENCIL_DESC Strukturen.
Weitere Informationen finden Sie in den strukturen D3D12_RENDER_PASS_BEGINNING_ACCESS und D3D12_RENDER_PASS_ENDING_ACCESS sowie in den Enumerationen D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE und D3D12_RENDER_PASS_ENDING_ACCESS_TYPE.
Rendern von Passflags
Der letzte an BeginRenderPass übergebene Parameter ist ein Renderpassflag (ein Wert aus der D3D12_RENDER_PASS_FLAGS-Enumeration).
enum D3D12_RENDER_PASS_FLAGS
{
D3D12_RENDER_PASS_FLAG_NONE = 0,
D3D12_RENDER_PASS_FLAG_ALLOW_UAV_WRITES = 0x1,
D3D12_RENDER_PASS_FLAG_SUSPENDING_PASS = 0x2,
D3D12_RENDER_PASS_FLAG_RESUMING_PASS = 0x4
};
UAV-Schreibvorgänge innerhalb eines Renderdurchlaufs
Unordered access view (UAV)-Schreibvorgänge sind innerhalb eines Renderdurchlaufs zulässig. Sie müssen jedoch explizit angeben, dass Sie UAV-Schreibvorgänge innerhalb des Renderdurchlaufs ausgeben, indem Sie D3D12_RENDER_PASS_FLAG_ALLOW_UAV_WRITES angeben, damit der Anzeigetreiber die Kacheln bei Bedarf deaktivieren kann.
UAV-Zugriffe müssen der oben beschriebenen Schreib-/Leseeinschränkung entsprechen (Schreibvorgänge in einem Renderdurchlauf sind erst nach einem nachfolgenden Renderdurchlauf gültig). UAV-Barrieren sind innerhalb eines Renderdurchlaufs nicht zulässig.
UAV-Bindungen (über Stammtabellen oder Stammdeskriptoren) werden in Renderdurchläufe geerbt und aus Renderdurchläufen weitergegeben.
Anhalten von Durchläufen und Fortsetzen von Durchläufen
Sie können einen gesamten Renderdurchlauf als "Anhaltend-Durchlauf" und/oder "Fortsetzen-Durchlauf" angeben. Ein Suspending-gefolgt-von-a-Resuming-Paar muss über identische Ansichten/Zugriffsflags zwischen den Durchläufen verfügen und darf keine dazwischen liegenden GPU-Vorgänge (z. B. Zeichnen, Dispatches, Verwerfen, Löschen, Kopieren, Updatekachelzuordnungen, Write-Buffer-Immediates, Abfragen, Abfrageauflösungen) zwischen dem anhaltenden Renderdurchlauf und dem fortsetzenden Renderdurchlauf aufweisen.
Der beabsichtigte Anwendungsfall ist Multithread-Rendering, bei dem beispielsweise vier Befehlslisten (jede mit ihren eigenen Renderdurchläufen) dieselben Renderziele als Ziel haben kann. Wenn Renderdurchläufe über Befehlslisten hinweg angehalten/fortgesetzt werden, müssen die Befehlslisten im gleichen Aufruf von ID3D12CommandQueue::ExecuteCommandListsausgeführt werden.
Ein Renderdurchlauf kann sowohl das Fortsetzen als auch das Anhalten sein. Im soeben angegebenen Multithreadbeispiel würden die Befehlslisten 2 und 3 von 1 bzw. 2 fortsetzen. Und gleichzeitig würden 2 und 3 jeweils bis 3 bzw. 4 anhalten.
Abfragen der Unterstützung von Renderdurchläufen
Sie können ID3D12Device::CheckFeatureSupport aufrufen, um abzufragen, in welchem Umfang ein Gerätetreiber und/oder die Hardware Renderdurchläufe effizient unterstützt.
D3D12_RENDER_PASS_TIER get_render_passes_tier(::ID3D12Device * pIDevice)
{
D3D12_FEATURE_DATA_D3D12_OPTIONS5 featureSupport{};
winrt::check_hresult(
pIDevice->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS5, &featureSupport, sizeof(featureSupport))
);
return featureSupport.RenderPassesTier;
}
...
D3D12_RENDER_PASS_TIER renderPassesTier{ get_render_passes_tier(pIDevice) };
Aufgrund der Zuordnungslogik der Laufzeit übergibt das Rendern immer die Funktion . Je nach Featureunterstützung bieten sie jedoch nicht immer einen Vorteil. Sie können codeähnlichen Code wie im obigen Codebeispiel verwenden, um zu bestimmen, ob bzw. wann es sich lohnt, Befehle als Renderdurchläufe auszugeben, und wenn dies definitiv kein Vorteil ist (d. h., wenn die Runtime nur der vorhandenen API-Oberfläche zugeordnet wird). Diese Überprüfung ist besonders wichtig, wenn Sie D3D11On12verwenden.
Eine Beschreibung der drei Supportebenen finden Sie in der D3D12_RENDER_PASS_TIER-Enumeration.