Passes de rendu Direct3D 12

La fonctionnalité de passes de rendu est nouvelle pour Windows 10, version 1809 (10.0 ; Build 17763), et elle introduit le concept d’une passe de rendu Direct3D 12. Une passe de rendu se compose d’un sous-ensemble des commandes que vous enregistrez dans une liste de commandes.

Pour déclarer l’endroit où chaque passe de rendu commence et se termine, vous imbriquez les commandes appartenant à qui passent à l’intérieur des appels à ID3D12GraphicsCommandList4::BeginRenderPass et EndRenderPass. Par conséquent, toute liste de commandes contient zéro, une ou plusieurs passes de rendu.

Scénarios

Les passes de rendu peuvent améliorer les performances de votre renderer s’il est basé sur Tile-Based Deferred Rendering (TBDR), entre autres techniques. Plus précisément, la technique permet à votre convertisseur d’améliorer l’efficacité du GPU en réduisant le trafic mémoire vers/depuis la mémoire hors puce en permettant à votre application de mieux identifier les exigences d’ordre de rendu des ressources et les dépendances de données.

Un pilote d’affichage écrit expressément pour tirer parti de la fonctionnalité de passes de rendu donne les meilleurs résultats. Toutefois, les API de passes de rendu peuvent s’exécuter même sur des pilotes préexistants (mais pas nécessairement avec des améliorations des performances).

Il s’agit des scénarios dans lesquels les passes de rendu sont conçues pour fournir de la valeur.

Autoriser votre application à éviter les charges/magasins inutiles de ressources à partir de/vers main mémoire sur une architecture tbDR (Deferred Rendering) Tile-Based

L’une des propositions de valeur des passes de rendu est qu’elle vous fournit un emplacement central pour indiquer les dépendances de données de votre application pour un ensemble d’opérations de rendu. Ces dépendances de données permettent au pilote d’affichage d’inspecter ces données au moment de la liaison/de la barrière, et d’émettre des instructions qui réduisent les charges de ressources/stocke de/vers main mémoire.

Autoriser votre architecture TBDR à des ressources persistantes de façon opportuniste dans le cache sur puce entre les passes de rendu (même dans des listes de commandes distinctes)

Notes

Plus précisément, ce scénario est limité aux cas où vous écrivez sur les mêmes cibles de rendu dans plusieurs listes de commandes.

Un modèle de rendu courant consiste pour votre application à effectuer le rendu vers les mêmes cibles de rendu sur plusieurs listes de commandes en série, même si les commandes de rendu sont générées en parallèle. Votre utilisation des passes de rendu dans ce scénario permet de combiner ces passes de telle sorte que (puisque l’application sait qu’elle reprendra le rendu sur la liste de commandes qui suit immédiatement) que le pilote d’affichage peut éviter un vidage à main mémoire sur les limites de la liste de commandes.

Responsabilités de votre application

Même avec la fonctionnalité de passes de rendu, ni le runtime Direct3D 12 ni le pilote d’affichage n’assument la responsabilité de déduire les opportunités de récommandation/d’éviter les chargements et les magasins. Pour tirer correctement parti de la fonctionnalité de passes de rendu, votre application a ces responsabilités.

  • Identifier correctement les dépendances de données/de classement pour ses opérations.
  • Commandez ses soumissions de manière à réduire les vidages (ainsi, réduisez votre utilisation des indicateurs de _PRESERVE ).
  • Utilisez correctement les barrières de ressources et suivez l’état des ressources.
  • Évitez les copies/effacements inutiles. Pour les identifier, vous pouvez utiliser les avertissements de performances automatisés de l’outil PIX sur Windows.

Utilisation de la fonctionnalité de passe de rendu

Qu’est-ce qu’une passe de rendu ?

Un passage de rendu est défini par ces éléments.

  • Ensemble de liaisons de sortie qui sont fixes pour la durée de la passe de rendu. Ces liaisons sont à une ou plusieurs vues cibles de rendu (RTV) et/ou à une vue de gabarit de profondeur (DSV).
  • Liste des opérations GPU qui ciblent cet ensemble de liaisons de sortie.
  • Métadonnées qui décrivent les dépendances de charge/de stockage pour toutes les liaisons de sortie ciblées par le passe de rendu.

Déclarer vos liaisons de sortie

Au début d’une passe de rendu, vous déclarez des liaisons à vos cibles de rendu et/ou à votre mémoire tampon de profondeur/gabarit. Il est facultatif de lier pour afficher la ou les cibles, et il est facultatif de lier à une mémoire tampon de profondeur/gabarit. Toutefois, vous devez établir une liaison à au moins l’une des deux, et dans l’exemple de code ci-dessous, nous liez aux deux.

Vous déclarez ces liaisons dans un appel à 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).
}

Vous définissez le premier champ de la structure D3D12_RENDER_PASS_RENDER_TARGET_DESC sur le handle de descripteur du processeur correspondant à une ou plusieurs vues cibles de rendu (RTV). De même, D3D12_RENDER_PASS_DEPTH_STENCIL_DESC contient le descripteur de processeur correspondant à une vue de gabarit de profondeur (DSV). Ces handles de descripteur de processeur sont les mêmes que ceux que vous passeriez à ID3D12GraphicsCommandList::OMSetRenderTargets. Et, tout comme avec OMSetRenderTargets, les descripteurs de processeur sont alignés à partir de leurs segments respectifs (descripteur de processeur) au moment de l’appel à BeginRenderPass.

Les V RTV et DSV ne sont pas hérités dans la passe de rendu. Ils doivent plutôt être définis. Les V RTV et DSV déclarés dans BeginRenderPass ne sont pas non plus propagés vers la liste des commandes. Au lieu de cela, ils sont dans un état non défini après la passe de rendu.

Passes et charges de travail de rendu

Vous ne pouvez pas imbriquer des passes de rendu et vous ne pouvez pas avoir une passe de rendu à cheval sur plusieurs listes de commandes (elles doivent commencer et se terminer lors de l’enregistrement dans une seule liste de commandes). Les optimisations conçues pour permettre une génération multithread efficace de passes de rendu sont décrites dans la section Indicateurs de passe de rendu ci-dessous.

Une écriture que vous effectuez à partir d’une passe de rendu n’est pas valide pour la lecture à partir d’une passe de rendu ultérieure. Cela empêche certains types de barrières à l’intérieur de la passe de rendu, par exemple, la barrière de RENDER_TARGET à SHADER_RESOURCE sur la cible de rendu actuellement liée. Pour plus d’informations, consultez la section Passes de rendu et barrières de ressources ci-dessous.

La seule exception à la contrainte écriture-lecture qui vient d’être mentionnée implique les lectures implicites qui se produisent dans le cadre du test de profondeur et du rendu de la cible. Par conséquent, ces API ne sont pas autorisées dans une passe de rendu (le runtime principal supprime la liste des commandes si l’une d’elles est appelée pendant l’enregistrement).

Passes de rendu et barrières de ressources

Vous ne pouvez pas lire ou consommer une écriture qui s’est produite dans la même passe de rendu. Certaines barrières ne sont pas conformes à cette contrainte, par exemple de D3D12_RESOURCE_STATE_RENDER_TARGET à *_SHADER_RESOURCE sur la cible de rendu actuellement liée (et la couche de débogage génère une erreur à cet effet). Toutefois, cette même barrière sur une cible de rendu qui a été écrite en dehors de la passe de rendu actuelle est conforme, car les écritures se terminent avant le début de la passe de rendu actuelle. Vous pouvez tirer parti de connaître certaines optimisations qu’un pilote d’affichage peut effectuer à cet égard. Étant donné une charge de travail conforme, un pilote d’affichage peut déplacer les barrières rencontrées dans votre passe de rendu vers le début de la passe de rendu. Là, ils peuvent être coalesciés (et ne pas interférer avec les opérations de mosaïque/bâillon). Il s’agit d’une optimisation valide, à condition que toutes vos écritures soient terminées avant le début de la passe de rendu actuelle.

Voici un exemple d’optimisation de pilote plus complet, qui suppose que vous disposez d’un moteur de rendu doté d’une conception de liaison de ressources de style pré-Direct3D 12, en faisant des barrières à la demande en fonction de la façon dont les ressources sont liées. Lors de l’écriture dans une vue d’accès non ordonnée (UAV) vers la fin d’un frame (à consommer dans le frame suivant), le moteur peut laisser la ressource dans l’état D3D12_RESOURCE_STATE_UNORDERED_ACCESS à la fin de l’image. Dans le cadre qui suit, lorsque le moteur va lier la ressource en tant qu’affichage de ressource de nuanceur (SRV), il constate que la ressource n’est pas dans l’état correct et émet un obstacle de D3D12_RESOURCE_STATE_UNORDERED_ACCESS à D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE. Si cette barrière se produit dans la passe de rendu, le pilote d’affichage est justifié en supposant que toutes les écritures ont déjà eu lieu en dehors de cette passe de rendu actuelle, et par conséquent (et c’est là que l’optimisation intervient) le pilote d’affichage peut déplacer la barrière jusqu’au début de la passe de rendu. Là encore, cela est valide, tant que votre code est conforme à la contrainte d’écriture-lecture décrite dans cette section et la dernière.

Il s’agit d’exemples de barrières conformes.

  • D3D12_RESOURCE_STATE_UNORDERED_ACCESS à D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT.
  • D3D12_RESOURCE_STATE_COPY_DEST à *_SHADER_RESOURCE.

Et ce sont des exemples de barrières non conformes.

  • D3D12_RESOURCE_STATE_RENDER_TARGET à n’importe quel état de lecture sur les V RTV/DSV actuellement liés.
  • D3D12_RESOURCE_STATE_DEPTH_WRITE à n’importe quel état de lecture sur les V RTV/DSV actuellement liés.
  • Toute barrière d’alias.
  • Barrières de vue d’accès non ordonné (UAV). 

Déclaration d’accès aux ressources

À l’heure BeginRenderPass , en plus de déclarer toutes les ressources qui servent de rtv et/ou de DSV dans ce passage, vous devez également spécifier leurs caractéristiques d’accès de début et de fin. Comme vous pouvez le voir dans l’exemple de code de la section Déclarer vos liaisons de sortie ci-dessus, vous effectuez cette opération avec les structures D3D12_RENDER_PASS_RENDER_TARGET_DESC et D3D12_RENDER_PASS_DEPTH_STENCIL_DESC .

Pour plus d’informations, consultez les structures D3D12_RENDER_PASS_BEGINNING_ACCESS et D3D12_RENDER_PASS_ENDING_ACCESS , ainsi que les énumérations D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE et D3D12_RENDER_PASS_ENDING_ACCESS_TYPE .

Indicateurs de passe de rendu

Le dernier paramètre passé à BeginRenderPass est un indicateur de passe de rendu (valeur de l’énumération 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
};

Écritures UAV dans une passe de rendu

Les écritures de vue d’accès non ordonné (UAV) sont autorisées dans une passe de rendu, mais vous devez indiquer spécifiquement que vous allez émettre des écritures UAV dans la passe de rendu en spécifiant D3D12_RENDER_PASS_FLAG_ALLOW_UAV_WRITES, afin que le pilote d’affichage puisse refuser le câblage si nécessaire.

Les accès UAV doivent suivre la contrainte d’écriture-lecture décrite ci-dessus (les écritures dans une passe de rendu ne sont pas valides pour la lecture tant qu’une passe de rendu suivante n’est pas valide). Les barrières UAV ne sont pas autorisées dans une passe de rendu.

Les liaisons UAV (via des tables racines ou des descripteurs racines) sont héritées en passes de rendu et sont propagées hors des passes de rendu.

Suspension-passes et reprise-passes

Vous pouvez indiquer une passe de rendu entière comme étant une suspension-passe et/ou une reprise-passe. Une paire suspending-followed-by-a-resuming doit avoir des vues/indicateurs d’accès identiques entre les passes, et peut ne pas avoir d’opérations GPU intermédiaires (par exemple, dessins, dispatches, discards, clears, copies, update-tile-mappings, write-buffer-immediates, requêtes, query resolves) entre la passe de rendu de suspension et la passe de rendu de reprise.

Le cas d’usage prévu est le rendu multithread, où, par exemple, quatre listes de commandes (chacune avec leurs propres passes de rendu) peuvent cibler les mêmes cibles de rendu. Lorsque les passes de rendu sont suspendues/reprises dans les listes de commandes, les listes de commandes doivent être exécutées dans le même appel à ID3D12CommandQueue::ExecuteCommandLists.

Une passe de rendu peut être en cours de reprise et de suspension. Dans l’exemple multithread que vous venez de donner, les listes de commandes 2 et 3 reprendraient respectivement 1 et 2. Et en même temps 2 et 3 seraient suspendus à 3 et 4, respectivement.

Requête pour la prise en charge des fonctionnalités de passes de rendu

Vous pouvez appeler ID3D12Device::CheckFeatureSupport pour savoir dans quelle mesure un pilote de périphérique et/ou le matériel prennent efficacement en charge les passes de rendu.

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

En raison de la logique de mappage du runtime, render passe toujours fonction. Toutefois, en fonction de la prise en charge des fonctionnalités, elles ne fourniront pas toujours d’avantage. Vous pouvez utiliser du code similaire à l’exemple de code ci-dessus pour déterminer s’il est utile d’émettre des commandes au fur et à mesure que le rendu passe, et quand ce n’est certainement pas un avantage (c’est-à-dire lorsque le runtime est simplement mappé à la surface d’API existante). Cette case activée est particulièrement importante si vous utilisez D3D11On12).

Pour obtenir une description des trois niveaux de prise en charge, consultez l’énumération D3D12_RENDER_PASS_TIER .