Vulkan. Guide du développeur

Je travaille en tant que traductrice technique pour la société informatique d'Izhevsk CG Tribe, qui m'a invité à contribuer à la communauté et à commencer à publier des traductions d'articles et de tutoriels intéressants.



Ici, je publierai la traduction du manuel de l'API Vulkan. Lien source - vulkan-tutorial.com . Puisqu'un autre utilisateur de Habr, kiwhy (https://habr.com/ru/users/kiwhy/), est engagé dans la traduction du même manuel, nous avons accepté de

partager les leçons entre nous. Dans mes publications, je fournirai des liens vers des chapitres traduits par kiwhy.



Contenu
1. Introduction



2. Bref aperçu



3. Environnement de développement



4. Dessiner un triangle



  1. Préparation au travail
  2. (pipeline)


5.



  1. Staging


6. Uniform-



  1. layout
  2. sets


7.



  1. Image view image sampler
  2. image sampler


8.



9.



10. -



11. Multisampling



FAQ







1. Introduction



Voir l'article de l'auteur kiwhy - habr.com/ru/post/462137



2. Aperçu



Contexte de Vulkan



Comment dessiner un triangle?



  1. Étape 1 - Instance et périphériques physiques
  2. Étape 2 - Unité logique et familles de files d'attente
  3. Étape 3 - Surface de la fenêtre et chaînes d'échange
  4. Étape 4 - Vues d'image et tampons d'image
  5. Étape 5 - Rendre les passes
  6. Étape 6 - Le pipeline graphique
  7. Étape 7 - Pool de commandes et tampons de commandes
  8. Étape 8 - Boucle principale
  9. conclusions


Concepts d'API



  1. Norme de formatage du code
  2. Couches de validation


Dans ce chapitre, nous allons commencer avec Vulkan et voir quels problèmes il peut résoudre. Nous décrirons les étapes nécessaires pour créer votre premier triangle. Cela vous donnera un aperçu de la norme et vous permettra de comprendre la logique derrière la présentation des chapitres suivants. Nous concluons par un aperçu de la structure de l'API Vulkan et des cas d'utilisation typiques.



Prérequis pour Vulkan



Comme les API graphiques précédentes, Vulkan est conçu comme une abstraction multiplateforme sur le GPU . Le principal problème avec la plupart de ces API est que lors de leur développement, du matériel graphique a été utilisé, limité à des fonctionnalités fixes. Les développeurs devaient fournir des données de sommets dans un format standard et étaient complètement dépendants des fabricants de GPU pour l'éclairage et les ombres.



Au fur et à mesure que l'architecture des cartes vidéo se développait, de plus en plus de fonctions programmables ont commencé à y apparaître. Toutes les nouvelles fonctionnalités devaient être combinées d'une manière ou d'une autre avec les API existantes. Cela a conduit à des abstractions imparfaites et à de nombreuses hypothèses de la part du pilote graphique sur la façon de traduire l'intention du programmeur en architectures graphiques modernes. Par conséquent, un grand nombre de mises à jour de pilotes sont publiées pour améliorer les performances des jeux. En raison de la complexité de ces pilotes, il existe souvent des écarts entre les fournisseurs, par exemple dans la syntaxe adoptée pour les shaders... En dehors de cela, la dernière décennie a également vu un afflux d'appareils mobiles dotés d'un matériel graphique puissant. Les architectures de ces GPU mobiles peuvent varier considérablement en fonction de la taille et des besoins en énergie. Un tel exemple est le rendu en mosaïque , qui peut fournir de meilleures performances grâce à un meilleur contrôle des fonctionnalités. Une autre limitation due à l'âge de l'API est la prise en charge limitée du multithreading, ce qui peut conduire à un goulot d'étranglement du côté du processeur.



Vulkan aide à résoudre ces problèmes car il a été conçu à partir de zéro pour les architectures graphiques modernes. Cela réduit la surcharge côté conducteur en permettant aux développeurs de décrire clairement leurs objectifs à l'aide d'une API détaillée. Vulkan vous permet de créer et d'envoyer des commandes en parallèle dans plusieurs threads. Il réduit également les écarts de compilation entre les shaders en passant à un format de bytecode normalisé et en utilisant un seul compilateur. Enfin, Vulkan rassemble les principales capacités des cartes graphiques actuelles en intégrant des capacités graphiques et informatiques dans une seule API.



Comment dessiner un triangle?



Nous examinerons rapidement les étapes nécessaires pour dessiner un triangle. Cela vous donnera un aperçu du processus. Une description détaillée de chaque concept sera donnée dans les chapitres suivants.



Étape 1 - Instance et périphériques physiques



Travailler avec Vulkan commence par configurer l'API Vulkan via VkInstance (instance). Une instance est créée à l'aide de la description de votre programme et des extensions que vous souhaitez utiliser. Après l'instanciation, vous pouvez interroger le matériel pris en charge par Vulkan et sélectionner un ou plusieurs VkPhysicalDevices pour effectuer des opérations. Vous pouvez vous renseigner sur les paramètres tels que la taille de la VRAM et les capacités de l'appareil pour sélectionner les appareils que vous souhaitez si vous préférez utiliser des cartes graphiques spécialisées.



Étape 2 - Périphérique logique et familles de files d'attente



Après avoir sélectionné le périphérique matériel approprié à utiliser, vous devez créer un VkDevice (périphérique logique), où vous décrirez plus en détail les fonctionnalités ( VkPhysicalDeviceFeatures ) que vous utiliserez, par exemple, le rendu dans plusieurs fenêtres. s (rendu multi-fenêtres) et flottants 64 bits. Vous devez également définir les familles de files d'attente que vous souhaitez utiliser. De nombreuses opérations effectuées avec Vulkan, telles que les commandes de dessin et les opérations en mémoire, sont effectuées de manière asynchrone après avoir été envoyées à VkQueue... Les files d'attente sont allouées à partir d'une famille de files d'attente, où chaque famille prend en charge un ensemble spécifique d'opérations. Par exemple, il peut y avoir des familles de files d'attente distinctes pour les transferts de données graphiques, de calcul et de mémoire. De plus, leur disponibilité peut être utilisée comme paramètre clé lors du choix d'un appareil physique. Certains appareils compatibles Vulkan n'offrent aucune capacité graphique, cependant, toutes les cartes graphiques modernes compatibles Vulkan prennent généralement en charge toutes les opérations de mise en file d'attente dont nous avons besoin.



Étape 3 - Surface de la fenêtre et chaînes de permutation



Si vous êtes intéressé par plus que le rendu hors écran, vous devez créer une fenêtre pour afficher les images rendues. Windows peut être créé à l'aide d'API de plate-forme native ou de bibliothèques telles que GLFW et SDL . Nous utiliserons GLFW pour ce tutoriel, que nous aborderons plus en détail dans le chapitre suivant.



Nous avons besoin de deux composants supplémentaires pour le rendu dans la fenêtre de l'application: la surface de la fenêtre ( VkSurfaceKHR) et la chaîne d'affichage ( VkSwapchainKHR). Faites attention au suffixeKHRce qui indique que ces objets font partie de l'extension Vulkan. L'API Vulkan est totalement indépendante de la plate-forme, nous devons donc utiliser une extension standardisée WSI (Window System Interface) pour interagir avec le gestionnaire de fenêtres. Surface est une abstraction de fenêtre multiplateforme pour le rendu qui est généralement créée en référençant un handle de fenêtre natif, comme HWNDsous Windows. Heureusement, la bibliothèque GLFW a une fonction intégrée pour travailler avec des détails spécifiques à la plate-forme.



Une chaîne de présentation est un ensemble de cibles de rendu. Sa tâche est de s'assurer que l'image en cours de rendu diffère de celle affichée à l'écran. Cela vous permet de garder une trace de ce que seules les images rendues sont affichées. Chaque fois que nous devons créer un cadre, nous devons faire une demande pour que la chaîne de spectacles nous fournisse une image à rendre. Une fois le cadre créé, l'image est renvoyée dans la chaîne d'affichage pour être affichée à l'écran à un moment donné. Le nombre de cibles de rendu et les conditions d'affichage des images finies à l'écran dépendent du mode actuel. Ces modes incluent la double mise en mémoire tampon (vsync) et la triple mise en mémoire tampon. Nous les couvrirons dans le chapitre sur la création d'une chaîne de spectacles.



Certaines plates-formes permettent le rendu directement à l'écran via des extensions VK_KHR_displayet VK_KHR_display_swapchainsans interagir avec aucun gestionnaire de fenêtres. Cela vous permet de créer une surface qui représente la totalité de l'écran et peut être utilisée, par exemple, pour implémenter votre propre gestionnaire de fenêtres.



Étape 4 - Vues d'image et framebuffers



Afin de dessiner dans l'image obtenue à partir de la chaîne d'affichage, nous devons l'envelopper dans un VkImageView et un VkFramebuffer . La vue d'image fait référence à une partie spécifique de l'image utilisée, et le framebuffer fait référence aux vues d'image, qui sont utilisées comme tampons de couleur, de profondeur et de gabarit. Puisqu'il peut y avoir de nombreuses images différentes dans la chaîne d'affichage, nous allons créer une vue d'image et un framebuffer pour chacun d'eux à l'avance et sélectionner l'image requise pendant le dessin.



Étape 5 -



Passes de rendu Les passes de rendu de Vulkan décrivent le type d'images utilisées pendant les opérations de rendu, comment elles sont utilisées et comment leur contenu doit être géré. Avant de dessiner le triangle, nous disons à Vulkan que nous voulons utiliser une seule image comme tampon de couleur et que nous devons l'effacer avant de dessiner. Si la passe de rendu décrit uniquement le type d'images utilisées comme tampons, alors VkFramebuffer associe en fait des images spécifiques à ces emplacements.



Étape 6 - Le pipeline



graphique Le pipeline graphique dans Vulkan est configuré en créant un objet VkPipeline . Il décrit l'état configurable de la carte vidéo, comme la taille de la fenêtre d'affichage ou le fonctionnement du tampon de profondeur, ainsi que l'état programmable à l'aide d'objets VkShaderModule . Les objets VkShaderModule sont créés à partir du bytecode du shader. Le pilote doit également spécifier les cibles de rendu qui seront utilisées dans le pipeline. Nous les définissons en référençant la passe de rendu.



L'une des caractéristiques les plus distinctives de Vulkan par rapport aux API existantes est que presque tous les paramètres de pipeline graphique système doivent être préconfigurés. Cela signifie que si vous souhaitez passer à un autre shader ou modifier légèrement la disposition des sommets, vous devez recréer complètement le pipeline graphique. Par conséquent, vous devrez créer de nombreux objets VkPipeline à l' avance pour toutes les combinaisons requises pour les opérations de rendu. Seuls certains paramètres de base, tels que la taille de la fenêtre et la couleur claire, peuvent être modifiés dynamiquement. Tous les états doivent être décrits explicitement. Ainsi, par exemple, il n'y a pas d'état de fusion de couleur par défaut.



Heureusement, comme le processus ressemble plus à une compilation à l'avance, au lieu de compiler à la volée, le pilote a plus d'opportunités d'optimisation et les performances sont plus prévisibles car des changements d'état importants, tels que le passage à un pipeline graphique différent, sont explicitement spécifiés.



Étape 7 - Pool de commandes et tampons de commandes



Comme mentionné, de nombreuses opérations dans Vulkan, telles que les opérations de dessin, doivent être mises en file d'attente. Avant d'envoyer des opérations, elles doivent être écrites dans VkCommandBuffer . Les tampons de commande proviennent de VkCommandPool , qui est associé à une famille spécifique de files d'attente. Pour dessiner un triangle simple, nous devons écrire un tampon de commande avec les opérations suivantes:



  • Lancer la passe de rendu
  • Lier le pipeline graphique
  • Dessinez 3 sommets
  • Fin de la passe de rendu


Étant donné que l'instance de l'image dans le framebuffer dépend de l'image que la chaîne d'affichage nous donnera, nous devons écrire un tampon de commande pour chaque image possible et sélectionner celle dont nous avons besoin pendant le dessin. Nous pouvons écrire le tampon de commande à chaque fois pour chaque image, mais c'est moins efficace.



Étape 8 - Boucle principale



Après avoir envoyé les commandes de dessin au tampon de commande, la boucle principale semble assez simple. Tout d'abord, nous obtenons l'image de la chaîne de spectacles avec vkAcquireNextImageKHR. Nous pouvons ensuite sélectionner le tampon de commande approprié pour cette image et l'exécuter avec vkQueueSubmit . Enfin, nous renvoyons l'image à la chaîne d'affichage pour l'afficher à l'aide de vkQueuePresentKHR.



Les opérations envoyées à la file d'attente sont effectuées de manière asynchrone. Par conséquent, nous devons utiliser des objets de synchronisation - des sémaphores - pour garantir le bon ordre de démarrage. Il est nécessaire de configurer l'exécution du tampon de commande de dessin de manière à ce qu'il ne soit effectué qu'après que l'image est extraite de la chaîne d'affichage, sinon une situation peut survenir lorsque nous commençons à rendre une image qui est encore en cours de lecture pour affichage à l'écran. L'appel vkQueuePresentKHR, à son tour, doit attendre la fin du rendu, pour lequel nous utiliserons le deuxième sémaphore. Il notifiera la fin du rendu.



Conclusions



Cet aperçu rapide vous donne un aperçu du travail avant de dessiner votre premier triangle. En réalité, il y a bien d'autres étapes. Celles-ci incluent l'allocation de tampons de vertex, la création de tampons uniformes et le chargement d'images de texture - tout cela que nous aborderons dans les chapitres suivants, mais pour l'instant, commençons simplement. Plus nous avançons, plus le matériau sera difficile. Notez que nous avons décidé de suivre la voie la plus délicate en intégrant initialement les coordonnées de vertex dans le shader de vertex au lieu d'utiliser le tampon de vertex. Cette décision est due au fait que pour gérer les tampons de vertex, vous devez d'abord vous familiariser avec les tampons de commande.



Résumons brièvement. Pour dessiner le premier triangle, nous avons besoin de:



  • Créer VkInstance
  • Sélectionnez une carte vidéo prise en charge ( VkPhysicalDevice )
  • Créez VkDevice et VkQueue pour dessiner et afficher
  • Créer une fenêtre, une surface de fenêtre et une chaîne d'affichage
  • Envelopper les images de la chaîne d'affichage dans VkImageView
  • Créer une passe de rendu qui définit les cibles de rendu et leur utilisation
  • Créer un framebuffer pour la passe de rendu
  • Configurer le pipeline graphique
  • Distribuez et écrivez des commandes de dessin dans la mémoire tampon pour chaque image de la chaîne d'affichage
  • Rendre les images en images reçues en envoyant le tampon de commande correct et en renvoyant les images à la chaîne d'affichage


Malgré le fait qu'il existe de nombreuses étapes, la signification de chacune d'elles sera claire dans les chapitres suivants. Si vous ne parvenez pas à comprendre une étape, revenez à ce chapitre.



Concepts d'API



Ce chapitre se termine par un bref aperçu de la manière dont les API Vulkan sont structurées à un niveau inférieur.



Norme de codage



Toutes les fonctions, énumérations et structures Vulkan sont étiquetées sous une rubrique vulkan.hincluse dans le SDK Vulkan développé par LunarG. L'installation du SDK sera traitée dans le chapitre suivant.



Les fonctions sont préfixées vken minuscules, les types énumérés (enum) et les structures sont préfixés Vket les valeurs énumérées sont préfixées VK_. L'API utilise largement les structures pour fournir des paramètres aux fonctions. Par exemple, les objets sont généralement créés selon le modèle suivant:



image



De nombreuses structures dans Vulkan vous obligent à spécifier explicitement le type de structure dans le membre sType. Un membre pNextpeut pointer vers une structure d'extension et sera toujours de typenullptr... Les fonctions qui créent ou détruisent un objet auront un paramètre VkAllocationCallbacks , qui vous permet d'utiliser votre propre allocateur de mémoire et qui dans le manuel aura également un type nullptr.



Presque toutes les fonctions renvoient VkResult , qui est VK_SUCCESSsoit un code d'erreur, soit un code d'erreur. La spécification indique les codes d'erreur que chaque fonction peut renvoyer et leur signification.



Couches de validation



Comme mentionné, Vulkan a été conçu pour fournir des performances élevées avec de faibles charges de pilote. Par conséquent, il comprend des capacités de détection et de correction automatique des erreurs très limitées. Si vous faites une erreur, le pilote plantera ou pire, continuera à fonctionner sur votre carte graphique, mais échouera sur d'autres cartes graphiques.



Par conséquent, Vulkan vous permet d'exécuter des validations avancées en utilisant une fonctionnalité appelée couches de validation... Les couches de validation sont des morceaux de code qui peuvent être insérés entre l'API et le pilote graphique pour effectuer une validation supplémentaire sur les paramètres de fonction et suivre les problèmes de gestion de la mémoire. Ceci est pratique car vous pouvez les démarrer pendant le développement, puis les désactiver complètement au démarrage du programme sans frais supplémentaires. N'importe qui peut écrire ses propres couches de validation, mais Vulkan SDK de LunarG fournit un ensemble standard que nous utiliserons tout au long du didacticiel. Vous devez également enregistrer une fonction de rappel pour recevoir les messages de débogage des couches.



Étant donné que les opérations dans Vulkan sont très détaillées et que les couches de validation sont assez étendues, il vous sera beaucoup plus facile de déterminer la cause de l'écran noir par rapport à OpenGL et Direct3D.



Il ne reste plus qu'une étape avant de commencer le codage, c'est la configuration de l'environnement de développement.



All Articles