Direct3D 12-Renderdurchläufe

Das Feature "Renderdurchläufe" 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 Pass gehören, innerhalb von 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 Verzögertes Rendering (TBDR) basiert. Genauer gesagt hilft die Technik Ihrem Renderer, die GPU-Effizienz zu verbessern, indem der Speicherdatenverkehr zu/vom Off-Chip-Speicher reduziert wird, indem Ihre Anwendung die Anforderungen an die Reihenfolge von Ressourcenrendering und Datenabhängigkeiten besser identifizieren kann.

Ein Anzeigetreiber, der ausdrücklich zur Nutzung der Funktion "Renderdurchläufe" geschrieben wurde, liefert die besten Ergebnisse. Renderdurchläufe-APIs können jedoch auch auf bereits vorhandenen Treibern ausgeführt werden (allerdings nicht unbedingt mit Leistungsverbesserungen).

Dies sind die Szenarien, in denen Renderdurchläufe so konzipiert sind, dass sie einen Wert bereitstellen.

Ermöglichen Sie Es Ihrer Anwendung, unnötiges Laden/Speichern von Ressourcen aus/bis Standard Arbeitsspeicher in einer TBDR-Architektur (Tile-Based Deferred Rendering) zu vermeiden.

Eines der Wertversprechen von Renderdurchläufen besteht darin, dass sie Ihnen einen zentralen Speicherort zur Verfügung stellt, an dem die Datenabhängigkeiten Ihrer Anwendung für eine Reihe von Renderingvorgängen angegeben werden. Diese Datenabhängigkeiten ermöglichen es dem Anzeigetreiber, diese Daten zur Bindungs-/Sperrzeit zu überprüfen und Anweisungen auszugeben, die ressourcenlasten/-speicher von/bis Standard Arbeitsspeicher minimieren.

Ermöglichen Sie Es Ihrer TBDR-Architektur, Ressourcen im On-Chip-Cache über Renderdurchläufe hinweg (auch in separaten Befehlslisten) opportunistisch persistent zu speichern.

Hinweis

Insbesondere ist dieses Szenario auf die Fälle beschränkt, in denen Sie über mehrere Befehlslisten hinweg an dieselben Renderziele schreiben.

Ein gängiges Renderingmuster besteht darin, dass Ihre Anwendung in mehreren Befehlslisten seriell auf dieselben Renderziele rendert, obwohl die Renderingbefehle parallel generiert werden. Ihre Verwendung von Renderdurchläufen in diesem Szenario ermöglicht es, diese Durchläufe so zu kombinieren (da die Anwendung weiß, dass das Rendern in der sofort erfolgreichen Befehlsliste fortgesetzt wird), dass der Anzeigetreiber eine Leerung von Standard Arbeitsspeicher an Befehlslistengrenzen vermeiden kann.

Aufgaben Ihrer Anwendung

Selbst mit dem Renderdurchläufen-Feature übernehmen weder die Direct3D 12-Runtime noch der Anzeigetreiber die Verantwortung für die Ableitung von Möglichkeiten, Umladungen und Speicher neu zu sortieren/zu vermeiden. Um das Renderdurchläufe-Feature ordnungsgemäß zu nutzen, hat Ihre Anwendung diese Aufgaben.

  • Identifizieren Sie die Daten-/Reihenfolgenabhängigkeiten für ihre Vorgänge ordnungsgemäß.
  • Ordnen Sie die Übermittlungen so an, dass Leerungen minimiert werden (minimieren Sie also die Verwendung von _PRESERVE Flags).
  • Nutzen Sie Ressourcenbarrieren ordnungsgemäß, und verfolgen Sie den Ressourcenstatus.
  • Vermeiden Sie unnötige Kopien/Löschen. Um diese zu identifizieren, können Sie die automatisierten Leistungswarnungen des TOOLS PIX unter Windows verwenden.

Verwenden des Renderpassfeatures

Was ist ein Renderdurchlauf?

Ein Renderdurchlauf wird durch diese Elemente definiert.

  • Ein Satz von Ausgabebindungen, die für die Dauer des Renderdurchlaufs festgelegt sind. Diese Bindungen gelten für eine oder mehrere Renderzielansichten (RTVs) und/oder für eine Tiefenschablonenansicht (DSV).
  • Eine Liste der GPU-Vorgänge, die auf den Satz von Ausgabebindungen ausgerichtet sind.
  • Metadaten, die die Lade-/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, eine Bindung an Renderziele zu erstellen, und es ist optional, eine Bindung an einen Tiefen-/Schablonenpuffer durchzuführen. Sie müssen jedoch an mindestens eine der beiden 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 einer oder mehreren Renderzielansichten (RTVs) entspricht. Ebenso enthält D3D12_RENDER_PASS_DEPTH_STENCIL_DESC das CPU-Deskriptorhandle, das einer Tiefenschablonenansicht (DSV) entspricht. Diese CPU-Deskriptorhandles sind dieselben, 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.

Die RTVs und DSV werden nicht an den Renderdurchlauf vererbt. Vielmehr müssen sie festgelegt werden. Die in BeginRenderPass deklarierten RTVs und DSV werden auch nicht an die Befehlsliste weitergegeben. Stattdessen befinden sie sich nach dem Renderdurchlauf in einem undefinierten Zustand.

Renderdurchläufe und Workloads

Renderdurchläufe können nicht geschachtelt werden, und es ist nicht möglich, dass ein Renderdurchlauf mehr als eine Befehlsliste enthält (sie müssen während der Aufzeichnung in einer einzelnen Befehlsliste beginnen und enden). Optimierungen, die eine effiziente Multithreadgenerierung von Renderdurchläufen ermöglichen, werden unten im Abschnitt Renderpassflags erläutert.

Ein Schreibvorgang, den Sie innerhalb eines Renderdurchlaufs ausführen, ist nicht gültig , damit Sie von bis zu einem nachfolgenden Renderdurchlauf lesen können. Dies schließt einige Arten von Barrieren innerhalb des Renderdurchlaufs aus, z. B. Barrieren von RENDER_TARGET zu SHADER_RESOURCE für das derzeit gebundene Renderziel. Weitere Informationen finden Sie weiter unten im Abschnitt Renderdurchläufe und Ressourcenbarrieren.

Die einzige Ausnahme von der soeben erwähnten Schreib-Leseeinschränkung umfasst die impliziten Lesevorgänge, die im Rahmen von Tiefentests und Renderzielmischungen auftreten. Daher sind diese APIs innerhalb eines Renderdurchlaufs nicht zulässig (die Kernruntime entfernt die Befehlsliste, wenn sie während der Aufzeichnung aufgerufen werden).

Renderdurchläufe und Ressourcenbarrieren

Sie dürfen keinen Schreibvorgang lesen oder nutzen, der innerhalb desselben Renderdurchlaufs aufgetreten ist. Bestimmte Barrieren entsprechen dieser Einschränkung nicht, z. B. von D3D12_RESOURCE_STATE_RENDER_TARGET bis *_SHADER_RESOURCE für das derzeit gebundene Renderziel (und die Debugebene führt zu einem fehlerbedingten Fehler). Dieselbe Barriere für ein Renderziel, das außerhalb des aktuellen Renderdurchlaufs geschrieben wurde, ist jedoch konform, da die Schreibvorgänge vor dem Start des aktuellen Renderdurchlaufs abgeschlossen werden. Möglicherweise profitieren Sie von der Kenntnis bestimmter Optimierungen, die ein Anzeigetreiber in dieser Hinsicht vornehmen kann. Bei einer konformen Workload kann ein Anzeigetreiber alle Hindernisse, die in Ihrem Renderdurchlauf auftreten, an den Anfang des Renderdurchlaufs verschieben. Dort können sie zusammengehaktet werden (und keine Tiling-/Binningvorgänge stören). Dies ist eine gültige Optimierung, sofern alle Schreibvorgänge abgeschlossen sind, bevor der aktuelle Renderdurchlauf gestartet wird.

Hier sehen Sie ein umfassenderes Beispiel für die Treiberoptimierung, bei dem davon ausgegangen wird, dass Sie über eine Rendering-Engine verfügen, die über einen Ressourcenbindungsentwurf im Vor-Direct3D-12-Stil verfügt, der je nach Ressourcenbindung Bedarfsbarrieren ausführt. Beim Schreiben in eine ungeordnete Zugriffsansicht (UAV) am Ende eines Frames (die im folgenden Frame verwendet werden soll) kann die Engine die Ressource am Ende des Frames im D3D12_RESOURCE_STATE_UNORDERED_ACCESS Zustand belassen. Wenn die Engine im folgenden Frame die Ressource als Shaderressourcenansicht (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. Wenn diese Barriere innerhalb des Renderdurchlaufs auftritt, ist der Anzeigetreiber berechtigt, davon auszugehen, dass alle Schreibvorgänge bereits außerhalb dieses aktuellen Renderdurchlaufs aufgetreten sind, und folglich (und hier kommt die Optimierung ins Ziel) der Anzeigetreiber die Barriere bis zum Anfang des Renderdurchlaufs verschieben kann. Auch dies ist gültig, solange Ihr Code der in diesem Abschnitt und im letzten Abschnitt beschriebenen Schreib-/Leseeinschränkung entspricht.

Dies sind Beispiele für übereinstimmende Barrieren.

  • D3D12_RESOURCE_STATE_UNORDERED_ACCESSD3D12_RESOURCE_STATE_INDIRECT_ARGUMENT.
  • D3D12_RESOURCE_STATE_COPY_DEST zu *_SHADER_RESOURCE.

Und dies sind Beispiele für nicht konforme Barrieren.

  • D3D12_RESOURCE_STATE_RENDER_TARGET zu einem beliebigen Lesestatus für derzeit gebundene RTVs/DSVs.
  • D3D12_RESOURCE_STATE_DEPTH_WRITE zu einem beliebigen Lesezustand für derzeit gebundene RTVs/DSVs.
  • Alle Aliasingbarrieren.
  • Barrieren für die ungeordnete Zugriffsansicht (UAV). 

Ressourcenzugriffsdeklaration

Zur BeginRenderPass-Zeit müssen Sie nicht nur alle Ressourcen deklarieren, die innerhalb dieses Durchlaufs als RTVs und/oder DSV dienen, sie müssen auch deren Anfangs- und Endzugriffsmerkmale angeben. Wie Sie im Codebeispiel im Abschnitt Deklarieren Ihrer Ausgabebindungen oben sehen können, führen Sie dies mit den D3D12_RENDER_PASS_RENDER_TARGET_DESC - und D3D12_RENDER_PASS_DEPTH_STENCIL_DESC-Strukturen aus.

Weitere Informationen finden Sie in den D3D12_RENDER_PASS_BEGINNING_ACCESS- und D3D12_RENDER_PASS_ENDING_ACCESS-Strukturen sowie in den D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE- und D3D12_RENDER_PASS_ENDING_ACCESS_TYPE-Enumerationen.

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

Schreibvorgänge der ungeordneten Zugriffsansicht (UAV) sind innerhalb eines Renderdurchlaufs zulässig. Sie müssen jedoch ausdrücklich angeben, dass Sie UAV-Schreibvorgänge innerhalb des Renderdurchlaufs ausgeben, indem Sie D3D12_RENDER_PASS_FLAG_ALLOW_UAV_WRITES angeben, sodass der Anzeigetreiber die Kacheln bei Bedarf deaktivieren kann.

UAV-Zugriffe müssen der oben beschriebenen Schreib-Lese-Einschränkung folgen (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 vererbt und aus Renderdurchläufen verteilt.

Suspending-passes und resuming-passes

Sie können einen gesamten Renderdurchlauf als anhaltend-pass und/oder resuming-pass angeben. Ein Suspending-followed-by-a-resuming-Paar muss über identische Ansichten/Zugriffsflags zwischen den Durchläufen verfügen und darf keine dazwischen liegenden GPU-Vorgänge (z. B. Ziehungen, Verworfen, Clears, Kopien, Update-Kachelzuordnungen, Schreibpuffer-Direktvorgänge, Abfragen, Abfrageauflösungen) zwischen dem anhaltenden Renderdurchlauf und dem fortsetzenden Renderdurchlauf aufweisen.

Der beabsichtigte Anwendungsfall ist Multithreadrendering, wobei beispielsweise vier Befehlslisten (jede mit eigenen Renderdurchläufen) auf dieselben Renderziele abzielen können. Wenn Renderdurchläufe über Befehlslisten hinweg angehalten/fortgesetzt werden, müssen die Befehlslisten im gleichen Aufruf von ID3D12CommandQueue::ExecuteCommandLists ausgeführt werden.

Ein Renderdurchlauf kann sowohl fortsetzen als auch anhaltend sein. Im soeben angegebenen Multithreadbeispiel werden die Befehlslisten 2 und 3 von 1 bzw. 2 fortgesetzt. Und gleichzeitig würden 2 und 3 zu 3 bzw. 4 ausgesetzt.

Featureunterstützung für Abfragen nach Renderdurchläufen

Sie können ID3D12Device::CheckFeatureSupport aufrufen, um abzufragen, inwieweit 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 Runtime funktionieren Renderdurchläufe immer. Je nach Featureunterstützung bieten sie jedoch nicht immer einen Vorteil. Sie können Code ähnlich dem obigen Codebeispiel verwenden, um zu bestimmen, ob/wann es sich lohnt, Befehle als Renderdurchläufe ausstellen zu lassen, und wann dies definitiv kein Vorteil ist (das heißt, wenn die Runtime nur der vorhandenen API-Oberfläche zugeordnet wird). Diese Überprüfung ist besonders wichtig, wenn Sie D3D11On12 verwenden.

Eine Beschreibung der drei Unterstützungsebenen finden Sie in der D3D12_RENDER_PASS_TIER-Enumeration .