Passaggi di rendering direct3D 12

La funzionalità di rendering passa è una novità per Windows 10, versione 1809 (10,0; Build 17763) e introduce il concetto di passaggio di rendering direct3D 12. Un passaggio di rendering è costituito da un subset dei comandi registrati in un elenco di comandi.

Per dichiarare dove inizia e termina ogni passaggio di rendering, annidare i comandi appartenenti a tale passaggio all'interno delle chiamate a ID3D12GraphicsCommandList4::BeginRenderPass e EndRenderPass. Di conseguenza, qualsiasi elenco di comandi contiene zero, uno o più passaggi di rendering.

Scenari

I passaggi di rendering possono migliorare le prestazioni del renderer se si basa su Tile-Based rendering posticipato (TBDR), tra le altre tecniche. In particolare, la tecnica consente al renderer di migliorare l'efficienza della GPU riducendo il traffico di memoria da e verso la memoria off-chip consentendo all'applicazione di identificare meglio i requisiti di ordinamento delle risorse e le dipendenze dei dati.

Un driver di visualizzazione scritto espressamente per sfruttare la funzionalità di passaggio di rendering offre i risultati migliori. Tuttavia, il rendering passa le API può essere eseguito anche su driver preesistenti (anche se, non necessariamente con miglioramenti delle prestazioni).

Questi sono gli scenari in cui i passaggi di rendering sono progettati per fornire valore.

Consentire all'applicazione di evitare carichi/archivi non necessari di risorse da/a memoria principale in un'architettura di rendering posticipato (TBDR) di Tile-Based

Una delle proposte di valore dei passaggi di rendering consiste nel fatto che fornisce una posizione centrale per indicare le dipendenze dei dati dell'applicazione per un set di operazioni di rendering. Queste dipendenze di dati consentono al driver di visualizzazione di esaminare questi dati in fase di associazione/barriera e di eseguire istruzioni che riducono al minimo i carichi di risorse/archivi da/verso la memoria principale.

Consentire all'architettura TBDR di eseguire risorse opportunistiche persistenti nella cache su chip nei passaggi di rendering (anche in elenchi di comandi separati)

Nota

In particolare, questo scenario è limitato ai casi in cui si scrive nella stessa destinazione di rendering tra più elenchi di comandi.

Un modello di rendering comune consente all'applicazione di eseguire il rendering nella stessa destinazione di rendering tra più elenchi di comandi in modo seriale, anche se i comandi di rendering vengono generati in parallelo. L'uso dei passaggi di rendering in questo scenario consente di combinare questi passaggi in modo tale (poiché l'applicazione sa che riprenderà il rendering nell'elenco di comandi con esito positivo immediato) che il driver di visualizzazione può evitare uno scaricamento nella memoria principale nei limiti dell'elenco di comandi.

Responsabilità dell'applicazione

Anche con la funzionalità di rendering passa, né il runtime Direct3D 12 né il driver di visualizzazione prendono la responsabilità di dedurre le opportunità di riordinare/evitare carichi e archivi. Per sfruttare correttamente la funzionalità di passaggio del rendering, l'applicazione ha queste responsabilità.

  • Identificare correttamente le dipendenze di dati/ordinamento per le relative operazioni.
  • Ordinare gli invii in modo da ridurre al minimo gli scaricamenti (quindi ridurre al minimo l'uso di flag di _PRESERVE ).
  • Usare correttamente le barriere delle risorse e tenere traccia dello stato delle risorse.
  • Evitare copie/cancellazioni non elaborate. Per identificare questi elementi, è possibile usare gli avvisi delle prestazioni automatizzati dello strumento PIX in Windows.

Uso della funzionalità di passaggio di rendering

Che cos'è un passaggio di rendering?

Un passaggio di rendering viene definito da questi elementi.

  • Set di associazioni di output fisse per la durata del passaggio di rendering. Queste associazioni sono destinate a una o più viste di destinazione di rendering (RTV) e/o a una visualizzazione depth stencil (DSV).
  • Elenco di operazioni GPU destinate a tale set di associazioni di output.
  • Metadati che descrivono le dipendenze di caricamento/archiviazione per tutte le associazioni di output destinate al passaggio di rendering.

Dichiarare le associazioni di output

All'inizio di un passaggio di rendering, dichiari associazioni alle destinazioni di rendering e/o al buffer depth/stencil. È facoltativo eseguire il binding alle destinazioni di rendering ed è facoltativo eseguire il binding a un buffer depth/stencil. È tuttavia necessario eseguire l'associazione ad almeno uno dei due e nell'esempio di codice riportato di seguito viene associato a entrambi.

Queste associazioni vengono dichiarate in una chiamata a 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).
}

Impostare il primo campo della struttura D3D12_RENDER_PASS_RENDER_TARGET_DESC sull'handle del descrittore della CPU corrispondente a una o più viste di destinazione di rendering (RTV). Analogamente, D3D12_RENDER_PASS_DEPTH_STENCIL_DESC contiene l'handle del descrittore della CPU corrispondente a una visualizzazione depth stencil (DSV). Gli handle del descrittore della CPU sono gli stessi che altrimenti si passano a ID3D12GraphicsCommandList::OMSetRenderTargets. Analogamente a OMSetRenderTargets, i descrittori della CPU vengono ritagliati dai rispettivi heap (descrittore CPU) al momento della chiamata a BeginRenderPass.

Gli RTV e le DSV non vengono ereditati nel passaggio di rendering. Piuttosto, devono essere impostati. Né gli RTV e le DSV dichiarate in BeginRenderPass vengono propagate all'elenco di comandi. Sono invece in uno stato non definito dopo il passaggio di rendering.

Eseguire il rendering dei passaggi e dei carichi di lavoro

Non è possibile annidare i passaggi di rendering e non è possibile avere un passaggio di rendering straddle più di un elenco di comandi (devono iniziare e terminare durante la registrazione in un singolo elenco di comandi). Le ottimizzazioni progettate per abilitare una generazione efficiente di passaggi di rendering multithread sono descritte nella sezione Eseguire il rendering dei flag, di seguito.

Una scrittura eseguita dall'interno di un passaggio di rendering non è valida per la lettura da fino a un passaggio di rendering successivo. Ciò impedisce alcuni tipi di barriere all'interno del passaggio di rendering, ad esempio la barriera da RENDER_TARGET a SHADER_RESOURCE nella destinazione di rendering attualmente associata. Per altre info, vedi la sezione Passaggi di rendering e barriere delle risorse, di seguito.

L'unica eccezione al vincolo di lettura di scrittura appena menzionata prevede le letture implicite che si verificano come parte del test di profondità e la fusione della destinazione di rendering. Pertanto, queste API non sono consentite all'interno di un passaggio di rendering (il runtime di base rimuove l'elenco di comandi se uno di essi viene chiamato durante la registrazione).

Eseguire il rendering dei passaggi e delle barriere alle risorse

Non è possibile leggere o utilizzare una scrittura che si è verificata all'interno dello stesso passaggio di rendering. Alcune barriere non sono conformi a questo vincolo, ad esempio da D3D12_RESOURCE_STATE_RENDER_TARGET a *_SHADER_RESOURCE sulla destinazione di rendering attualmente associata e il livello di debug genererà un errore a tale effetto. Tuttavia, la stessa barriera su una destinazione di rendering scritta all'esterno del passaggio di rendering corrente è conforme, perché le scritture verranno completate prima dell'inizio del passaggio di rendering corrente. È possibile trarre vantaggio dalla conoscenza di determinate ottimizzazioni che un driver di visualizzazione può fare in questo senso. Dato un carico di lavoro conforme, un driver di visualizzazione potrebbe spostare eventuali barriere rilevate nel passaggio di rendering all'inizio del passaggio di rendering. Lì, possono essere coalesci (e non interferire con eventuali operazioni di tiling/binning). Si tratta di un'ottimizzazione valida, a condizione che tutte le scritture siano state completate prima dell'avvio del passaggio di rendering corrente.

Ecco un esempio di ottimizzazione del driver più completo, che presuppone che si disponga di un motore di rendering con una progettazione di associazione di risorse pre-Direct3D 12, eseguendo barriere su richiesta in base alla modalità di associazione delle risorse. Quando si scrive in una visualizzazione di accesso non ordinata (UAV) verso la fine di un frame (da utilizzare nel frame seguente), il motore potrebbe lasciare la risorsa nello stato D3D12_RESOURCE_STATE_UNORDERED_ACCESS alla conclusione del frame. Nel frame che segue, quando il motore passa per associare la risorsa come visualizzazione risorse shader (SRV), troverà che la risorsa non è nello stato corretto e genererà una barriera da D3D12_RESOURCE_STATE_UNORDERED_ACCESS a D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE. Se tale barriera si verifica all'interno del passaggio di rendering, il driver di visualizzazione è giustificato presupponendo che tutte le scritture siano già state eseguite al di fuori di questo passaggio di rendering corrente e di conseguenza (e di seguito viene visualizzata l'ottimizzazione) il driver di visualizzazione potrebbe spostare la barriera fino all'inizio del passaggio di rendering. Anche in questo caso, questa operazione è valida, purché il codice sia conforme al vincolo di lettura di scrittura descritto in questa sezione e l'ultimo.

Questi sono esempi di barriere conformi.

  • D3D12_RESOURCE_STATE_UNORDERED_ACCESS a D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT.
  • D3D12_RESOURCE_STATE_COPY_DEST a *_SHADER_RESOURCE.

E questi sono esempi di barriere non conformi.

  • D3D12_RESOURCE_STATE_RENDER_TARGET a qualsiasi stato di lettura in RTV/DSV attualmente associati.
  • D3D12_RESOURCE_STATE_DEPTH_WRITE a qualsiasi stato di lettura in RTV/DSV attualmente associati.
  • Qualsiasi barriera di aliasing.
  • Barriere UAV (Unrdered Access View). 

Dichiarazione di accesso alle risorse

All'ora beginRenderPass , oltre a dichiarare tutte le risorse che fungono da RTV e/o DSV all'interno di tale passaggio, è necessario specificare anche le relative caratteristiche di accesso iniziale e finale. Come si può vedere nell'esempio di codice nella sezione Dichiarare le associazioni di output sopra, eseguire questa operazione con le strutture D3D12_RENDER_PASS_RENDER_TARGET_DESC e D3D12_RENDER_PASS_DEPTH_STENCIL_DESC .

Per altre informazioni, vedere le strutture D3D12_RENDER_PASS_BEGINNING_ACCESS e D3D12_RENDER_PASS_ENDING_ACCESS e le enumerazioni D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE e D3D12_RENDER_PASS_ENDING_ACCESS_TYPE .

Flag di passaggio di rendering

L'ultimo parametro passato a BeginRenderPass è un flag di passaggio di rendering (valore dell'enumerazione D3D12_RENDER_PASS_FLAGS ).

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
};

Scritture UAV all'interno di un passaggio di rendering

Le scritture UAV (Unrdered Access View) sono consentite all'interno di un passaggio di rendering, ma è necessario indicare in modo specifico che si emettono scritture UAV all'interno del passaggio di rendering specificando D3D12_RENDER_PASS_FLAG_ALLOW_UAV_WRITES, in modo che il driver di visualizzazione possa rifiutare esplicitamente il tiling, se necessario.

Gli accessi UAV devono seguire il vincolo di lettura di scrittura descritto in precedenza (le scritture in un passaggio di rendering non sono valide per la lettura fino a quando non viene eseguito un passaggio di rendering successivo). Le barriere UAV non sono consentite all'interno di un passaggio di rendering.

Le associazioni UAV (tramite tabelle radice o descrittori radice) vengono ereditate nei passaggi di rendering e vengono propagate al di fuori dei passaggi di rendering.

Sospensione dei passaggi e ripresa dei passaggi

È possibile indicare un intero passaggio di rendering come passaggio di sospensione e/o un passaggio di ripresa. Una coppia di sospensione seguita da una ripresa deve avere flag di visualizzazione/accesso identici tra i passaggi e potrebbe non avere alcuna operazione di GPU in caso di intervento (ad esempio, disegna, invia, elimina, cancella, copie, update-tile-mappings, write-buffer-immediate, query, query risolte) tra il passaggio di rendering di sospensione e il passaggio di rendering di ripresa.

Il caso d'uso previsto è il rendering multithread, in cui si supponga che quattro elenchi di comandi (ognuno con i propri passaggi di rendering) possa essere destinato alle stesse destinazioni di rendering. Quando i passaggi di rendering vengono sospesi/ripresi tra elenchi di comandi, gli elenchi di comandi devono essere eseguiti nella stessa chiamata a ID3D12CommandQueue::ExecuteCommandLists.

Un passaggio di rendering può essere ripreso e sospeso. Nell'esempio multithread appena specificato, gli elenchi di comandi 2 e 3 riprenderebbero rispettivamente da 1 e 2. E allo stesso tempo 2 e 3 sarebbero sospesi rispettivamente a 3 e 4.

Query per il supporto delle funzionalità dei passaggi di rendering

È possibile chiamare ID3D12Device::CheckFeatureSupport per eseguire una query sulla misura in cui un driver di dispositivo e/o l'hardware supporta in modo efficiente il rendering.

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) };

A causa della logica di mapping del runtime, il rendering passa sempre la funzione . Tuttavia, a seconda del supporto delle funzionalità, non fornirà sempre un vantaggio. È possibile usare codice simile all'esempio di codice precedente per determinare se/quando vale la pena eseguire comandi durante il rendering e quando non è sicuramente un vantaggio ( ovvero quando il runtime esegue solo il mapping all'area API esistente). L'esecuzione di questo controllo è particolarmente importante se si usa D3D11On12.

Per una descrizione dei tre livelli di supporto, vedere l'enumerazione D3D12_RENDER_PASS_TIER .