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 in Aufrufen von ID3D12GraphicsCommandList4::BeginRenderPass und EndRenderPass gehören. Daher enthält jede Befehlsliste null, eine oder mehrere Renderdurchläufe.

Szenarien

Renderdurchläufe können die Leistung Ihres Renderers verbessern, wenn es auf Tile-Based Verzögerten Rendering (TBDR) basiert, unter anderem. Die Technik hilft Ihrem Renderer, die GPU-Effizienz zu verbessern, indem der Speicherdatenverkehr auf/vom Off-Chip-Speicher reduziert wird, indem Ihre Anwendung die Ressourcenrenderungsanforderungen und Datenabhängigkeiten besser identifizieren kann.

Ein Anzeigetreiber, der ausdrücklich geschrieben wurde, um das Renderdurchläufe-Feature zu nutzen, bietet die besten Ergebnisse. Renderdurchlauf-APIs können jedoch auch bei vorhandenen Treibern ausgeführt werden (obwohl nicht notwendigerweise leistungsverbesserungen).

Dies sind die Szenarien, in denen Renderdurchläufe für einen Wert ausgelegt sind.

Ermöglichen Sie Ihrer Anwendung, unnötige Lasten/Speicher von Ressourcen aus/in den Hauptspeicher einer Tile-Based verzögerten Renderingarchitektur (TBDR) zu vermeiden.

Eine der Wertversprechen von Renderdurchläufen besteht darin, dass sie Ihnen einen zentralen Speicherort bietet, um die Datenabhängigkeiten Ihrer Anwendung für eine Reihe von Renderingvorgängen anzugeben. Mit diesen Datenabhängigkeiten kann der Anzeigetreiber diese Daten zur Bindungszeit/Barriere überprüfen und Anweisungen zur Minimierung von Ressourcenlasten/Speicher von/zu Hauptspeichern ausgeben.

Zulassen, dass Ihre TBDR-Architektur opportunistisch persistente Ressourcen im On-Chip-Cache über Renderdurchläufe hinweg (auch in separaten Befehlslisten)

Hinweis

Insbesondere ist dieses Szenario auf die Fälle beschränkt, in denen Sie in mehrere Befehlslisten an dasselbe Renderziel schreiben.

Ein gängiges Renderingmuster ist für Ihre Anwendung, um in mehreren Befehlslisten fortlaufend auf das gleiche Renderziel zu rendern, auch wenn die Renderingbefehle parallel generiert werden. Ihre Verwendung von Renderdurchläufen in diesem Szenario ermöglicht es, diese Pässe so kombiniert zu werden (da die Anwendung weiß, dass das Rendern in der unmittelbar erfolgreichen Befehlsliste fortgesetzt wird), dass der Anzeigetreiber einen Leerlauf zum Hauptspeicher an Befehlslistengrenzen vermeiden kann.

Verantwortlichkeiten Ihrer Anwendung

Selbst bei der Renderdurchführungsfunktion übernehmen weder die Direct3D 12-Runtime noch der Anzeigetreiber die Verantwortung für die Ableitung von Möglichkeiten zum Erneuten Bestellen/Vermeiden von Lade- und Speichervorgängen. Um das Renderdurchläufe-Feature ordnungsgemäß nutzen zu können, hat Ihre Anwendung diese Verantwortlichkeiten.

  • Identifizieren Sie Daten-/Sortierabhängigkeiten für ihre Vorgänge ordnungsgemäß.
  • Ordnen Sie ihre Übermittlungen so an, dass Löschungen minimiert werden (so minimieren Sie die Verwendung von _PRESERVE Flags).
  • Verwenden Sie Ressourcenbarrieren richtig, und verfolgen Sie den Ressourcenstatus.
  • Vermeiden Sie nicht benötigte Kopien/Löschungen. Um diese zu identifizieren, können Sie die automatisierten Leistungswarnungen von PIX auf Windows Tool verwenden.

Verwenden des Renderdurchlauffeatures

Was ist ein Renderdurchlauf?

Ein Renderdurchlauf wird durch diese Elemente definiert.

  • Eine Reihe von Ausgabebindungen, die für die Dauer des Renderdurchlaufs behoben sind. Diese Bindungen sind eine oder mehrere Renderzielansichten (RTVs) und/oder eine Tiefenschablonenansicht (DSV).
  • Eine Liste der GPU-Vorgänge, die auf Ausgabebindungen abzielen.
  • Metadaten, die die Lade-/Speicherabhängigkeiten für alle ausgabebezogenen Bindungen für den Renderdurchlauf beschreiben.

Deklarieren Ihrer Ausgabebindungen

Am Anfang eines Renderdurchlaufs deklarieren Sie Bindungen an Ihr Renderziel und/oder an den Tiefen-/Schablonenpuffer. Es ist optional, eine Bindung an renderziel(n) zu binden, und es ist optional, eine Bindung an einen Tiefen-/Schablonenpuffer zu binden. 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 den CPU-Deskriptor-Handle fest, der einem oder mehreren Renderzielansichten (RTVs) entspricht. Ebenso enthält D3D12_RENDER_PASS_DEPTH_STENCIL_DESC den CPU-Deskriptor-Handle, der einer Tiefenschablonenansicht (DSV) entspricht. Diese CPU-Deskriptor-Handle sind dieselben, die Sie andernfalls an ID3D12GraphicsCommandList::OMSetRenderTargets übergeben würden. Ebenso 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 geerbt. Stattdessen müssen sie festgelegt werden. Außerdem werden die RTVs und DSV in BeginRenderPass in der Befehlsliste verteilt. Stattdessen befinden sie sich im nicht definierten Zustand nach dem Renderdurchlauf.

Renderdurchläufe und Workloads

Sie können Renderdurchläufe nicht verschachteln, und Sie können keinen Renderdurchlauf mehr als eine Befehlsliste haben (sie müssen beginnen und enden, während sie in einer einzigen Befehlsliste aufgezeichnet werden). Optimierungen, die für die effiziente Multithread-Generierung von Renderdurchläufen entwickelt wurden, werden im Abschnitt render pass Flags unten erläutert.

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

Die einzige Ausnahme der schreibgeschützten Einschränkung, die gerade erwähnt wurde, umfasst die impliziten Lesevorgänge, die im Rahmen von Tiefentests und Renderzielmischungen auftreten. Diese APIs sind also innerhalb eines Renderdurchlaufs nicht zulässig (die Kernlaufzeit entfernt die Befehlsliste, wenn sie während der Aufzeichnung aufgerufen werden).

Renderdurchläufe und Ressourcenbarrieren

Sie können keinen Schreibzugriff aus demselben Renderdurchlauf lesen oder nutzen. Bestimmte Barrieren entsprechen dieser Einschränkung nicht, z. B. von D3D12_RESOURCE_STATE_RENDER_TARGET bis *_SHADER_RESOURCE auf dem aktuell gebundenen Renderziel (und die Debugebene tritt auf diese Auswirkung auf). Diese Barriere für ein Renderziel, das außerhalb des aktuellen Renderdurchlaufs geschrieben wurde, ist jedoch konform, da die Schreibvorgänge vor dem aktuellen Renderdurchlauf abgeschlossen werden. Sie können von bestimmten Optimierungen profitieren, die ein Anzeigetreiber in dieser Hinsicht vornehmen kann. Angesichts einer konformen Arbeitsauslastung kann ein Anzeigetreiber alle Hindernisse in Ihrem Renderdurchlauf an den Anfang des Renderdurchlaufs verschieben. Dort können sie gekohlt werden (und keine Tiling-/Binning-Vorgänge stören). Dies ist eine gültige Optimierung, sofern alle Schreibvorgänge abgeschlossen sind, bevor der aktuelle Renderdurchlauf gestartet wird.

Im Folgenden finden Sie ein vollständiges Beispiel für die Treiberoptimierung, das davon ausgeht, dass Sie über ein Renderingmodul verfügen, das über ein Vorabdesign im Direct3D 12-Stil für Ressourcenbindung verfügt. Dabei handelt es sich um Hindernisse für die Nachfrage basierend auf der Bindung von Ressourcen. Beim Schreiben in eine ungeordnete Zugriffsansicht (UAV) am Ende eines Frames (zu verwenden im folgenden Frame) kann das Modul die Ressource im D3D12_RESOURCE_STATE_UNORDERED_ACCESS Zustand am Ende des Frames verlassen. Wenn das Modul die Ressource im folgenden Rahmen als Shaderressourcenansicht (SRV) bindet, wird festgestellt, dass die Ressource nicht im richtigen Zustand ist, und es stellt eine Barriere von D3D12_RESOURCE_STATE_UNORDERED_ACCESS auf D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE. Wenn diese Barriere innerhalb des Renderdurchlaufs auftritt, ist der Anzeigetreiber gerechtfertigt, wenn vorausgesetzt, dass alle Schreibvorgänge bereits außerhalb dieses aktuellen Renderdurchlaufs aufgetreten sind, und folglich (und hier ist die Optimierung) der Anzeigetreiber die Barriere bis zum Anfang des Renderdurchlaufs verschieben kann. Dies ist erneut gültig, solange Der Code der in diesem Abschnitt und der letzten beschriebenen Schreibzugriffseinschränkung entspricht.

Dies sind Beispiele für konforme Barrieren.

  • D3D12_RESOURCE_STATE_UNORDERED_ACCESS , um D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT.
  • D3D12_RESOURCE_STATE_COPY_DEST auf *_SHADER_RESOURCE.

Und dies sind Beispiele für nicht konforme Barrieren.

  • D3D12_RESOURCE_STATE_RENDER_TARGET zu jedem Lesestatus auf derzeit gebundenen RTVs/DSVs.
  • D3D12_RESOURCE_STATE_DEPTH_WRITE zu jedem Lesestatus auf derzeit gebundenen RTVs/DSVs.
  • Alle Aliasingbarrieren.
  • Ungeordnete Zugriffsansichtsbarrieren (UAV). 

Ressourcenzugriffsdeklaration

Zur BeginRenderPass-Zeit sowie zum Deklarieren aller Ressourcen, die als RTVs und/oder DSV innerhalb dieses Pass dienen, müssen Sie auch die Anfangs- und Endzugriffseigenschaften angeben. Wie Sie im Codebeispiel im Abschnitt "Ausgabebindungen deklarieren " oben sehen können, führen Sie dies mit den strukturen D3D12_RENDER_PASS_RENDER_TARGET_DESC und D3D12_RENDER_PASS_DEPTH_STENCIL_DESC aus.

Weitere Informationen finden Sie in den strukturen D3D12_RENDER_PASS_BEGINNING_ACCESS und D3D12_RENDER_PASS_ENDING_ACCESSsowie in den D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE und D3D12_RENDER_PASS_ENDING_ACCESS_TYPE Aufzählungen.

Renderdurchlaufkennzeichnungen

Der letzte Parameter, der an BeginRenderPass übergeben wird, ist ein Renderdurchlaufkennzeichen (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 schreibt in einem Renderdurchlauf

Ungeordnete Zugriffsansichts-Schreibvorgänge (UAV) sind innerhalb eines Renderdurchlaufs zulässig. Sie müssen jedoch ausdrücklich angeben, dass UAV-Schreibvorgänge innerhalb des Renderdurchlaufs ausgegeben werden, indem sie D3D12_RENDER_PASS_FLAG_ALLOW_UAV_WRITES angeben, sodass der Anzeigetreiber bei Bedarf die Tilung deaktivieren kann.

UAV-Zugriffe müssen der oben beschriebenen Schreibzugriffseinschränkung folgen (Schreibvorgänge in einem Renderdurchlauf sind erst gültig, wenn ein nachfolgender Renderdurchlauf gelesen werden kann). UAV-Barrieren sind in einem Renderdurchlauf nicht zulässig.

UAV-Bindungen (über Stammtabellen oder Stammdeskriptoren) werden in Renderdurchläufe geerbt und werden aus Renderdurchläufen verteilt.

Anhalten und Fortsetzen von Pässen

Sie können einen gesamten Renderdurchlauf als angehaltenen Pass und/oder einen Resuming-Pass angeben. Ein angehaltenes Gefolgt-von-a-Resuming-Paar muss identische Ansichten/Zugriffskennzeichnungen zwischen den Pässen aufweisen und möglicherweise keine dazwischen liegenden GPU-Vorgänge (z. B. Zeichnungen, Verteiler, Verwerfen, Löschen, Kopien, Aktualisierungskachelzuordnungen, Schreibpuffer-direkt, Abfragen, Abfragelöse) zwischen dem angehaltenen Renderdurchlauf und dem resumierenden Renderdurchlauf aufweisen.

Der beabsichtigte Anwendungsfall ist multithreadiertes Rendering, wobei vier Befehlslisten (jeweils mit eigenen Renderdurchgängen) auf dieselben Renderziele ausgerichtet werden 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 die Fortsetzung als auch das Anhalten sein. Im soeben angegebenen Mehrthreadbeispiel würde die Befehlsliste 2 und 3 jeweils von 1 bis 2 fortgesetzt. Und gleichzeitig würde 2 und 3 jeweils auf 3 und 4 ausgesetzt sein.

Abfrage für die Unterstützung von Renderdurchläufen

Sie können ID3D12Device::CheckFeatureSupport aufrufen, um den Umfang abzufragen, in dem 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 funktioniert rendern immer. Je nach Featureunterstützung bieten sie jedoch nicht immer einen Vorteil. Sie können Code verwenden, der dem obigen Codebeispiel ähnelt, um zu bestimmen, ob/wann sie während der Ausgabe von Befehlen als Renderdurchgängen wert ist und wann es definitiv kein Vorteil ist (d. a. wenn die Laufzeit nur der vorhandenen API-Oberfläche zugeordnet ist). Die Ausführung dieser Überprüfung ist besonders wichtig, wenn Sie D3D11On12 verwenden).

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