Vulkan. Guide du développeur. Chaîne d'échange

Je continue à publier des traductions du manuel de l'API Vulkan (le lien vers l'original est vulkan-tutorial.com ), et aujourd'hui je souhaite partager la traduction d'un nouveau chapitre - Swap chain de la section Dessin d'un triangle, sous-section Présentation.



Teneur
1.



2.



3.



4.







  1. (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









Chaîne d'échange





Vulkan n'a pas de framebuffer par défaut, il a donc besoin d'une infrastructure avec des tampons où les images seront rendues avant d'être affichées. Cette infrastructure est appelée chaîne d'échange et doit être explicitement créée dans Vulkan. La chaîne d'échange est une file d'images en attente d'être affichées à l'écran. Le programme demande d'abord un objet image(VkImage)



dans lequel dessiner et après le rendu, le renvoie dans la file d'attente. Le fonctionnement de la file d'attente dépend des paramètres, mais la tâche principale de la chaîne d'échange est de synchroniser la sortie des images avec le taux de rafraîchissement de l'écran.



Vérification du support de la chaîne d'échange



Certaines cartes vidéo spécialisées n'ont pas de sortie d'affichage et ne peuvent donc pas afficher d'images à l'écran. De plus, le mappage d'écran est lié au système de fenêtres et ne fait pas partie du noyau Vulkan. Par conséquent, nous devons connecter l'extension VK_KHR_swapchain



.



Tout d'abord isDeviceSuitable



, changeons la fonction pour vérifier si l'extension est prise en charge. Nous avons déjà travaillé avec la liste des extensions prises en charge auparavant, il ne devrait donc pas y avoir de difficultés. Notez que le fichier d'en-tête Vulkan fournit une macro pratique VK_KHR_SWAPCHAIN_EXTENSION_NAME



qui est définie comme " VK_KHR_swapchain



". L'avantage de cette macro est que si vous faites une faute d'orthographe, le compilateur vous en avertira.



Commençons par déclarer une liste d'extensions requises.



const std::vector<const char*> deviceExtensions = {
    VK_KHR_SWAPCHAIN_EXTENSION_NAME
};
      
      





Pour une vérification supplémentaire, créons une nouvelle fonction checkDeviceExtensionSupport



appelée depuis isDeviceSuitable



:



bool isDeviceSuitable(VkPhysicalDevice device) {
    QueueFamilyIndices indices = findQueueFamilies(device);

    bool extensionsSupported = checkDeviceExtensionSupport(device);

    return indices.isComplete() && extensionsSupported;
}

bool checkDeviceExtensionSupport(VkPhysicalDevice device) {
    return true;
}
      
      





Modifions le corps de la fonction pour vérifier si toutes les extensions dont nous avons besoin sont dans la liste des extensions prises en charge.



bool checkDeviceExtensionSupport(VkPhysicalDevice device) {
    uint32_t extensionCount;
    vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr);

    std::vector<VkExtensionProperties> availableExtensions(extensionCount);
    vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data());

    std::set<std::string> requiredExtensions(deviceExtensions.begin(), deviceExtensions.end());

    for (const auto& extension : availableExtensions) {
        requiredExtensions.erase(extension.extensionName);
    }

    return requiredExtensions.empty();
}
      
      





Ici, j'avais l'habitude std::set<std::string>



de stocker les noms des extensions requises mais pas encore confirmées. Vous pouvez également utiliser une boucle imbriquée comme dans une fonction checkValidationLayerSupport



. La différence de performance n'est pas significative.



Maintenant, exécutons le programme et assurez-vous que notre carte vidéo est adaptée à la création d'une chaîne d'échange. Notez que la présence d'une file d'attente d'affichage implique déjà la prise en charge de l'extension de chaîne d'échange. Cependant, il est préférable de s'en assurer explicitement.



Connexion d'extensions



Pour utiliser la chaîne d'échange, vous devez d'abord activer l'extension VK_KHR_swapchain



. Pour ce faire, modifions légèrement le remplissage VkDeviceCreateInfo



lors de la création du périphérique logique:



createInfo.enabledExtensionCount = static_cast<uint32_t>(deviceExtensions.size());
createInfo.ppEnabledExtensionNames = deviceExtensions.data();
      
      





Demande d'informations sur le support de la chaîne d'échange



Vérifier seul pour voir si la chaîne de swap est disponible ne suffit pas. La création de la chaîne de swap implique beaucoup plus de configuration, nous devons donc demander plus d'informations.



Au total, vous devez vérifier 3 types de propriétés:



  • Capacités de base de la surface, telles que le nombre min / max d'images dans la chaîne d'échange, la largeur min / max et la hauteur des images
  • Format de surface (format pixel, espace colorimétrique)
  • Modes de fonctionnement disponibles


Pour travailler avec ces données, nous utiliserons la structure:



struct SwapChainSupportDetails {
    VkSurfaceCapabilitiesKHR capabilities;
    std::vector<VkSurfaceFormatKHR> formats;
    std::vector<VkPresentModeKHR> presentModes;
};
      
      





Créons maintenant une fonction querySwapChainSupport



qui remplit cette structure.



SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) {
    SwapChainSupportDetails details;

    return details;
}
      
      





Commençons par les capacités de surface. Ils sont faciles à interroger et à revenir à la structure VkSurfaceCapabilitiesKHR



.



vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities);
      
      





Cette fonction accepte les fichiers VkPhysicalDevice



et VkSurfaceKHR



. Chaque fois que nous demandons des fonctionnalités prises en charge, ces deux paramètres seront les premiers, car ce sont des composants clés de la chaîne de swap.



L'étape suivante consiste à interroger les formats de surface pris en charge. Pour ce faire, exécutons le rituel déjà familier avec un double appel de fonction:



uint32_t formatCount;
vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr);

if (formatCount != 0) {
    details.formats.resize(formatCount);
    vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data());
}
      
      





Assurez-vous d'allouer suffisamment d'espace dans le vecteur pour obtenir tous les formats disponibles.



De la même manière, nous demandons les modes de fonctionnement supportés en utilisant la fonction vkGetPhysicalDeviceSurfacePresentModesKHR



:



uint32_t presentModeCount;
vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr);

if (presentModeCount != 0) {
    details.presentModes.resize(presentModeCount);
    vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data());
}
      
      





Lorsque toutes les informations nécessaires sont dans la structure, ajoutez la fonction isDeviceSuitable



pour vérifier si la chaîne d'échange est prise en charge. Pour les besoins de ce didacticiel, nous supposerons que s'il existe au moins un format d'image pris en charge et un mode pris en charge pour la surface de la fenêtre, la chaîne d'échange est prise en charge.



bool swapChainAdequate = false;
if (extensionsSupported) {
    SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device);
    swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty();
}
      
      





Il vous suffit de demander le support de la chaîne d'échange après avoir vérifié que l'extension est disponible.



La dernière ligne de la fonction devient:



return indices.isComplete() && extensionsSupported && swapChainAdequate;
      
      





Choix des paramètres de la chaîne d'échange



Si swapChainAdequate



vrai, la chaîne d'échange est prise en charge. Mais la chaîne d'échange peut avoir plusieurs modes. Écrivons quelques fonctions pour trouver les paramètres appropriés pour créer la chaîne d'échange la plus efficace.



Au total, nous mettons en évidence 3 types de paramètres:

  • format de surface (profondeur de couleur)
  • mode de fonctionnement (conditions de changement de trame sur l'écran)
  • extension de swap (résolution des images dans la chaîne de swap)


Pour chaque paramètre, nous rechercherons une valeur «idéale», et si elle n'est pas disponible, nous utiliserons une logique pour choisir ce qui est.



Format de surface



Ajoutons une fonction pour sélectionner un format:



VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector<VkSurfaceFormatKHR>& availableFormats) {

}
      
      





Plus tard, nous passerons un membre formats



de la structure SwapChainSupportDetails



comme argument.



Chaque élément availableFormats



contient des membres format



et colorSpace



. Le champ format



définit le nombre et les types de canaux. Par exemple, cela VK_FORMAT_B8G8R8A8_SRGB



signifie que nous avons des canaux B, V, R et alpha de 8 bits chacun, pour un total de 32 bits par pixel. Un indicateur VK_COLOR_SPACE_SRGB_NONLINEAR_KHR



dans le champ colorSpace



indique si l'espace colorimétrique SRGB est pris en charge. Notez que dans une version antérieure de la spécification, cet indicateur était appelé VK_COLORSPACE_SRGB_NONLINEAR_KHR



.



Nous utiliserons SRGB comme espace colorimétrique. SRGB est un standard pour la représentation des couleurs dans les images, il reproduit mieux les couleurs perçues. C'est pourquoi nous utiliserons également l'un des formats SRGB comme format de couleur - VK_FORMAT_B8G8R8A8_SRGB



.



Passons en revue la liste et vérifions si la combinaison dont nous avons besoin est disponible:



for (const auto& availableFormat : availableFormats) {
    if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
        return availableFormat;
    }
}
      
      





Sinon, nous pouvons trier les formats disponibles du plus approprié au moins approprié, mais dans la plupart des cas, nous pouvons simplement prendre le premier de la liste.



VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector<VkSurfaceFormatKHR>& availableFormats) {
    for (const auto& availableFormat : availableFormats) {
        if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
            return availableFormat;
        }
    }

    return availableFormats[0];
}

      
      





Heures d'ouverture



Le mode de fonctionnement est peut-être le paramètre le plus important pour la chaîne d'échange, car il détermine les conditions de changement de trame à l'écran.



Il existe quatre modes disponibles dans Vulkan:



  • VK_PRESENT_MODE_IMMEDIATE_KHR



    : , , , .
  • VK_PRESENT_MODE_FIFO_KHR



    : . , . , . , .
  • VK_PRESENT_MODE_FIFO_RELAXED_KHR



    : , . . .
  • VK_PRESENT_MODE_MAILBOX_KHR



    : ceci est une autre variante du deuxième mode. Au lieu de bloquer le programme lorsque la file d'attente est pleine, les images de la file d'attente sont remplacées par de nouvelles. Ce mode est adapté à la mise en œuvre du triple buffering. Avec lui, vous pouvez éviter l'apparition d'artefacts avec une faible latence.


Seul le mode est garanti disponible VK_PRESENT_MODE_FIFO_KHR



, donc encore une fois nous devrons écrire une fonction pour trouver le meilleur mode disponible:



VkPresentModeKHR chooseSwapPresentMode(const std::vector<VkPresentModeKHR>& availablePresentModes) {
    return VK_PRESENT_MODE_FIFO_KHR;
}
      
      





Personnellement, je trouve préférable d'utiliser le triple buffering. Il évite les artefacts à faible latence.



Alors parcourons la liste pour vérifier les modes disponibles:



VkPresentModeKHR chooseSwapPresentMode(const std::vector<VkPresentModeKHR>& availablePresentModes) {
    for (const auto& availablePresentMode : availablePresentModes) {
        if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) {
            return availablePresentMode;
        }
    }

    return VK_PRESENT_MODE_FIFO_KHR;
}
      
      





Étendue du swap



Il reste à configurer la dernière propriété. Pour ce faire, ajoutez une fonction:



VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) {

}
      
      





L'étendue de l'échange est la résolution des images dans la chaîne d'échange, qui correspond presque toujours à la résolution de la fenêtre (en pixels) où les images sont rendues. Nous avons la plage autorisée dans la structure VkSurfaceCapabilitiesKHR



. Vulkan nous dit quelle résolution nous devons définir en utilisant un champ currentExtent



(correspond à la taille de la fenêtre). Cependant, certains gestionnaires de fenêtres autorisent différentes résolutions. Pour cela, une valeur spéciale pour la largeur et la hauteur currentExtent



est spécifiée - la valeur maximale du type uint32_t



. Dans ce cas, à partir de l'intervalle entre minImageExtent



et, maxImageExtent



nous choisirons la résolution qui correspond le mieux à la résolution de la fenêtre. L'essentiel est de spécifier correctement les unités de mesure.



GLFW utilise deux unités de mesure: les pixels et les coordonnées d'écran . Ainsi, la résolution {WIDTH, HEIGHT}



que nous avons spécifiée lors de la création de la fenêtre est mesurée en coordonnées d'écran. Mais comme Vulkan fonctionne avec des pixels, la résolution de la chaîne d'échange doit également être spécifiée en pixels. Si vous utilisez un écran haute résolution (tel que l'écran Retina d'Apple), les coordonnées de l'écran ne correspondent pas aux pixels: en raison de la densité de pixels plus élevée, la résolution de la fenêtre est plus élevée en pixels qu'en coordonnées d'écran. Puisque Vulkan ne corrigera pas l'autorisation de la chaîne d'échange pour nous, nous ne pouvons pas utiliser l'autorisation d'origine {WIDTH, HEIGHT}



. Au lieu de cela, nous devrions utiliser glfwGetFramebufferSize



pour interroger la résolution de la fenêtre en pixels avant de la mapper sur les résolutions d'image minimale et maximale.



#include <cstdint> // Necessary for UINT32_MAX

...

VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) {
    if (capabilities.currentExtent.width != UINT32_MAX) {
        return capabilities.currentExtent;
    } else {
        int width, height;
        glfwGetFramebufferSize(window, &width, &height);

        VkExtent2D actualExtent = {
            static_cast<uint32_t>(width),
            static_cast<uint32_t>(height)
        };

        actualExtent.width = std::max(capabilities.minImageExtent.width, std::min(capabilities.maxImageExtent.width, actualExtent.width));
        actualExtent.height = std::max(capabilities.minImageExtent.height, std::min(capabilities.maxImageExtent.height, actualExtent.height));

        return actualExtent;
    }
}
      
      





Fonction max



et min



est utilisé pour limiter les valeurs width



et height



dans les résolutions disponibles. N'oubliez pas d'inclure le fichier d'en-tête <algorithm>



pour utiliser les fonctions.



Création d'une chaîne d'échange



Nous disposons désormais de toutes les informations nécessaires pour créer une chaîne de swap adaptée.



Créons une fonction createSwapChain



et appelons-la initVulkan



après avoir créé le périphérique logique.



void initVulkan() {
    createInstance();
    setupDebugMessenger();
    createSurface();
    pickPhysicalDevice();
    createLogicalDevice();
    createSwapChain();
}

void createSwapChain() {
    SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice);

    VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats);
    VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes);
    VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities);
}
      
      





Vous devez maintenant décider du nombre d'objets image dans la chaîne d'échange. La mise en œuvre précise le montant minimum requis pour les travaux:



uint32_t imageCount = swapChainSupport.capabilities.minImageCount;
      
      





Cependant, si vous n'utilisez que ce minimum, vous devez parfois attendre que le pilote termine les opérations internes pour obtenir l'image suivante. Par conséquent, il est préférable d'en demander au moins un de plus que le minimum spécifié:



uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1;
      
      





Il est important de ne pas dépasser le montant maximum. Une valeur 0



indique qu'aucun maximum n'est spécifié.



if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) {
    imageCount = swapChainSupport.capabilities.maxImageCount;
}
      
      





La chaîne d'échange est un objet Vulkan, vous devez donc remplir la structure pour la créer. Le début de la structure nous est déjà familier:



VkSwapchainCreateInfoKHR createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
createInfo.surface = surface;
      
      





Tout d'abord, la surface est spécifiée, à laquelle la chaîne d'échange est attachée, puis - informations pour créer des objets image:



createInfo.minImageCount = imageCount;
createInfo.imageFormat = surfaceFormat.format;
createInfo.imageColorSpace = surfaceFormat.colorSpace;
createInfo.imageExtent = extent;
createInfo.imageArrayLayers = 1;
createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
      
      





Dans imageArrayLayers



spécifie le nombre de couches dont chaque image est constituée. Il y aura toujours de la valeur ici 1



, sauf si, bien sûr, ce sont des images stéréo. Le champ de bits imageUsage



indique pour quelles opérations les images obtenues à partir de la chaîne d'échange seront utilisées. Dans le didacticiel, nous leur rendrons directement le rendu, mais vous pouvez d'abord effectuer le rendu sur une image distincte, par exemple pour le post-traitement. Dans ce cas, utilisez la valeur VK_IMAGE_USAGE_TRANSFER_DST_BIT



et utilisez l'opération de mémoire pour le transfert.



QueueFamilyIndices indices = findQueueFamilies(physicalDevice);
uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()};

if (indices.graphicsFamily != indices.presentFamily) {
    createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
    createInfo.queueFamilyIndexCount = 2;
    createInfo.pQueueFamilyIndices = queueFamilyIndices;
} else {
    createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
    createInfo.queueFamilyIndexCount = 0; // Optional
    createInfo.pQueueFamilyIndices = nullptr; // Optional
}
      
      





Vous devez ensuite spécifier comment gérer les objets images utilisés dans plusieurs familles de files d'attente. Cela est vrai pour les cas où une famille graphique et une famille d'affichage sont des familles différentes. Nous rendrons les images dans la file d'attente graphique, puis les enverrons dans la file d'attente d'affichage.



Il existe deux façons de traiter les images avec un accès à partir de plusieurs files d'attente:



  • VK_SHARING_MODE_EXCLUSIVE



    : un objet appartient à une famille de files d'attente et la propriété doit être transférée explicitement avant de l'utiliser dans une autre famille de files d'attente. Cette méthode offre les meilleures performances.

  • VK_SHARING_MODE_CONCURRENT



    : Les objets peuvent être utilisés dans plusieurs familles de files d'attente sans en transférer explicitement la propriété.



Si nous avons plusieurs files d'attente, nous utiliserons VK_SHARING_MODE_CONCURRENT



. Cette méthode vous oblige à spécifier à l'avance entre quelles familles de files d'attente la propriété sera partagée. Cela peut être fait en utilisant les paramètres queueFamilyIndexCount



et pQueueFamilyIndices



. Si la famille de la file d'attente graphique et la famille de la file d'attente d'affichage sont identiques, ce qui est plus courant, utilisez VK_SHARING_MODE_EXCLUSIVE



.



createInfo.preTransform = swapChainSupport.capabilities.currentTransform;
      
      





Vous pouvez spécifier que les images de la chaîne d'échange sont appliquées avec l'une des transformations prises en charge ( supportedTransforms



entrée capabilities



), par exemple, pivoter de 90 degrés dans le sens des aiguilles d'une montre ou retourner horizontalement. Pour ne pas appliquer de transformations, il suffit de partir currentTransform



.



createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
      
      





Le champ compositeAlpha



indique s'il faut utiliser le canal alpha pour fusionner avec d'autres fenêtres dans le système de fenêtrage. Vous n'aurez probablement pas besoin d'un canal alpha, alors laissez-le VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR



.



createInfo.presentMode = presentMode;
createInfo.clipped = VK_TRUE;
      
      





Le domaine presentMode



parle de lui-même. Si nous le mettons VK_TRUE



dans le champ clipped



, alors nous ne sommes pas intéressés par les pixels cachés (par exemple, si une partie de notre fenêtre est couverte par une autre fenêtre). Vous pouvez toujours désactiver le découpage si vous avez besoin de lire les pixels, mais pour l'instant, laissons le découpage activé.



createInfo.oldSwapchain = VK_NULL_HANDLE;
      
      





Le dernier champ reste - oldSwapChain



. Si la chaîne de swap devient invalide, par exemple en raison du redimensionnement de la fenêtre, elle devra être recréée à partir de zéro et dans le champ, oldSwapChain



spécifiez un lien vers l'ancienne chaîne de swap. C'est un sujet complexe que nous aborderons dans un chapitre ultérieur. Pour l'instant, disons que nous n'avons qu'une seule chaîne d'échange.



Ajoutons un membre de classe pour stocker l'objet VkSwapchainKHR



:



VkSwapchainKHR swapChain;
      
      





Il vous suffit maintenant d'appeler vkCreateSwapchainKHR



pour créer la chaîne d'échange:



if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) {
    throw std::runtime_error("failed to create swap chain!");
}
      
      





Les paramètres suivants sont transmis à la fonction: périphérique logique, informations de chaîne de swap, un allocateur personnalisé facultatif et un pointeur pour écrire le résultat. Pas de surprises. La chaîne d'échange doit être détruite en utilisant vkDestroySwapchainKHR



avant que l'appareil ne soit détruit:



void cleanup() {
    vkDestroySwapchainKHR(device, swapChain, nullptr);
    ...
}
      
      





Maintenant, exécutons le programme pour nous assurer que la chaîne d'échange a été créée avec succès. Si vous recevez un message d'erreur ou un message du genre « vkGetInstanceProcAddress SteamOverlayVulkanLayer.dll»



, accédez à la section FAQ .



Essayons de supprimer la ligne createInfo.imageExtent = extent;



avec les couches de validation activées. L'un des niveaux de validation détectera immédiatement l'erreur et nous informera:



image



Obtenir une image à partir d'une chaîne d'échange



Maintenant que la chaîne de swap a été créée, il reste à récupérer les descripteurs VkImages . Ajoutons un membre de classe pour stocker les descripteurs:



std::vector<VkImage> swapChainImages;
      
      





Les objets image de la chaîne d'échange seront détruits automatiquement après la destruction de la chaîne d'échange elle-même, il n'est donc pas nécessaire d'ajouter de code de nettoyage.



Immédiatement après l'appel, vkCreateSwapchainKHR



ajoutez le code pour obtenir les descripteurs. N'oubliez pas que nous n'avons spécifié que le nombre minimum d'images dans la chaîne d'échange, ce qui signifie qu'il peut y en avoir plus. Par conséquent, nous demandons d'abord le nombre réel d'images à l'aide de la fonction vkGetSwapchainImagesKHR



, puis nous allouons l'espace nécessaire dans le conteneur et le rappelons vkGetSwapchainImagesKHR



pour obtenir les descripteurs.



vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr);
swapChainImages.resize(imageCount);
vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data());
      
      





Et la dernière chose - enregistrez le format et la résolution des images de la chaîne d'échange dans des variables de classe. Nous en aurons besoin à l'avenir.



VkSwapchainKHR swapChain;
std::vector<VkImage> swapChainImages;
VkFormat swapChainImageFormat;
VkExtent2D swapChainExtent;

...

swapChainImageFormat = surfaceFormat.format;
swapChainExtent = extent;
      
      





Nous avons maintenant une image à dessiner et à afficher. Dans le chapitre suivant, nous allons vous montrer comment configurer une image à utiliser comme cibles de rendu, et commencer avec le pipeline graphique et les commandes de dessin!



C ++



All Articles