Partager via


Passes de rendu Direct3D 12

La fonctionnalité de passage de rendu est nouvelle pour Windows 10, version 1809 (10.0 ; Build 17763), et il 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 où chaque passe de rendu commence et se termine, vous imbriquez les commandes appartenant à ce passage à l’intérieur des appels à ID3D12GraphicsCommandList4 ::BeginRenderPass et EndRenderPass. Par conséquent, toute liste de commandes contient zéro, un ou plusieurs passes de rendu.

Scénarios

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

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

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

Autoriser votre application à éviter les charges/magasins inutiles de ressources depuis/vers la mémoire principale sur une architecture de rendu différé (TBDR) 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/magasins de ressources de/vers la mémoire principale.

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

Note

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

Un modèle de rendu courant est que votre application s’affiche sur la ou 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 manière (car l’application sait qu’elle reprendra le rendu sur la liste de commandes réussie immédiatement) que le pilote d’affichage peut éviter un vidage en mémoire principale sur les limites de la liste de commandes.

Responsabilités de votre application

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

  • Identifiez correctement les dépendances de données/de classement pour ses opérations.
  • Commandez ses soumissions d’une manière qui réduit les vidages (par conséquent, réduisez votre utilisation de _PRESERVE indicateurs).
  • 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 passage de rendu

Qu’est-ce qu’un de rendu?

Une passe de rendu est définie par ces éléments.

  • Ensemble de liaisons de sortie qui sont fixes pendant la durée du passage de rendu. Ces liaisons sont à un ou plusieurs affichages 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 chargement/magasin pour toutes les liaisons de sortie ciblées par le passage de rendu.

Déclarer vos liaisons de sortie

Au début d’une passe de rendu, vous déclarez des liaisons à votre ou vos cibles de rendu et/ou à votre mémoire tampon de profondeur/gabarit. Il est facultatif de lier à la ou les cibles de rendu, et il est facultatif de lier à une mémoire tampon de profondeur/gabarit. Toutefois, vous devez établir une liaison à au moins l’un des deux, et dans l’exemple de code ci-dessous, nous liez les 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 descripteur du processeur correspondant à un ou plusieurs affichages cibles de rendu (RTV). De même, D3D12_RENDER_PASS_DEPTH_STENCIL_DESC contient le descripteur processeur correspondant à une vue de gabarit de profondeur (DSV). Ces descripteurs de processeur sont les mêmes que ceux que vous transmettez autrement à ID3D12GraphicsCommandList ::OMSetRenderTargets. Et, comme avec OMSetRenderTargets, les descripteurs d’UC sont alignés à partir de leurs segments de mémoire respectifs (descripteur d’UC) 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. Ni les V RTV et DSV déclarés dans BeginRenderPass propagés à la liste de commandes. Au lieu de cela, ils sont dans un état non défini après la passe de rendu.

Rendu des passes et des charges de travail

Vous ne pouvez pas imbriquer les passes de rendu et vous ne pouvez pas avoir de passe de rendu 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 rendu, ci-dessous.

Une écriture que vous effectuez à partir d’une passe de rendu n’est pas valide pour vous permettre de lire à partir d’une passe de rendu ultérieure. Cela empêche certains types d’obstacles de 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 Les passes de rendu et les barrières de ressources, ci-dessous.

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

Afficher les passes et les 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 la connaissance de 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 obstacles rencontrés dans votre passe de rendu au début de la passe de rendu. Là, elles peuvent être coalescées (et n’interfèrent pas avec les opérations de mosaïque/binning). Il s’agit d’une optimisation valide, à condition que toutes vos écritures aient terminé avant le démarrage du pass de rendu actuel.

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 12 pré-Direct3D, ce qui fait des obstacles à 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’une trame (à consommer dans l’image suivante), le moteur peut laisser la ressource dans l’état D3D12_RESOURCE_STATE_UNORDERED_ACCESS à la fin de l’image. Dans la trame qui suit, lorsque le moteur va lier la ressource en tant qu’affichage de ressource de nuanceur (SRV), il trouve que la ressource n’est pas dans l’état correct et qu’elle émet une barrière entre D3D12_RESOURCE_STATE_UNORDERED_ACCESS et 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 se sont déjà produites en dehors de de cette passe de rendu actuelle, et par conséquent (et voici où l’optimisation vient), 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 de lecture en écriture 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.

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

  • D3D12_RESOURCE_STATE_RENDER_TARGET à n’importe quel état de lecture sur les VT/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ée (UAV). 

Déclaration d’accès aux ressources

À BeginRenderPass temps, ainsi que la déclaration de toutes les ressources qui servent de rtv et/ou de DSV au sein de cette passe, vous devez également spécifier leurs caractéristiques de début et de fin d’accès. Comme vous pouvez le voir dans l’exemple de code dans la Déclarer vos liaisons de sortie section 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 passage de rendu

Le dernier paramètre passé à BeginRenderPass est un indicateur de passage 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 en mode d’accès non ordonné (UAV) sont autorisées dans une passe de rendu, mais vous devez indiquer spécifiquement que vous émettrez 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 tiling si nécessaire.

Les accès UAV doivent suivre la contrainte de lecture en écriture décrite ci-dessus (les écritures dans une passe de rendu ne sont pas valides pour lire jusqu’à ce qu’une passe de rendu ultérieure). 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 dans les passes de rendu et sont propagées hors des passes de rendu.

Suspension des passes et reprise de passes

Vous pouvez indiquer une passe de rendu entière comme étant une passe d’interruption et/ou une passe de reprise. Une paire d’interruptions suivie d’une paire de reprise doit avoir des affichages/indicateurs d’accès identiques entre les passes et peut ne pas avoir d’opérations GPU intermédiaires (par exemple, dessins, répartitions, abandons, effacements, copies, mappages de vignettes, écriture-tampon-immédiats, requêtes, résolutions de requête) entre le passage de rendu suspendu et le passage de rendu de reprise.

Le cas d’usage prévu est un rendu multithread, où supposons que 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/reprise sur plusieurs 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 à la fois reprise et suspension. Dans l’exemple multithread juste donné, les listes de commandes 2 et 3 reprendraient respectivement de 1 à 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 passage de rendu

Vous pouvez appeler ID3D12Device ::CheckFeatureSupport pour interroger la mesure dans laquelle 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, le rendu passe toujours la fonction. Toutefois, selon la prise en charge des fonctionnalités, ils ne fournissent pas toujours d’avantages. Vous pouvez utiliser du code similaire à l’exemple de code ci-dessus pour déterminer si/quand il vaut la peine de émettre des commandes en tant que passes de rendu, et quand il n’est certainement pas un avantage (autrement dit, lorsque le runtime est simplement mappé à l’aire d’API existante). L’exécution de cette vérification 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.