Teneur
1.
2.
3.
4.
5.
6. Uniform-
7.
8.
9.
10. -
11. Multisampling
FAQ
2.
3.
4.
-
-
- Window surface
- Swap chain
- Image views
- (pipeline)
5.
- Staging
6. Uniform-
- layout
- sets
7.
- Image view image sampler
- image sampler
8.
9.
10. -
11. Multisampling
FAQ
Chaîne d'échange
- Vérification du support de la chaîne d'échange
- Connexion d'extensions
- Demande d'informations sur le support de la chaîne d'échange
- Choix des paramètres de la chaîne d'échange
- Création d'une chaîne d'échange
- Obtenir une image à partir d'une 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:
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 ++