Ce billet est une continuation du billet précédent " Vulkan. Guide du développeur. Dessiner un triangle ", il est dédié à la traduction du chapitre Validation des couches.
Contenu
Couches de validation
- Que sont les couches de validation?
- Utilisation des couches de validation
- Interception des messages de débogage
- Instance de débogage de Vulkan (instance de débogage)
- Essai
- RĂ©glages
Que sont les couches de validation?
La conception de l'API Vulkan est basĂ©e sur l'idĂ©e d'une charge minimale sur le pilote, donc par dĂ©faut, les capacitĂ©s de dĂ©tection d'erreurs sont sĂ©vĂšrement limitĂ©es. MĂȘme des erreurs simples telles que des valeurs incorrectes dans les Ă©numĂ©rations ou le passage de pointeurs nuls ne sont gĂ©nĂ©ralement pas traitĂ©es explicitement et entraĂźnent des plantages ou un comportement indĂ©fini. Ătant donnĂ© que travailler avec Vulkan nĂ©cessite une description dĂ©taillĂ©e de chaque action, de telles erreurs peuvent se produire assez souvent.
Pour rĂ©soudre ce problĂšme, Vulkan utilise des couches de validation . Les couches de validation sont des composants facultatifs qui peuvent ĂȘtre connectĂ©s Ă des appels de fonction pour effectuer des opĂ©rations supplĂ©mentaires. Les opĂ©rations suivantes peuvent ĂȘtre effectuĂ©es dans les couches de validation:
- Vérification des valeurs des paramÚtres conformément aux spécifications pour détecter les erreurs
- Suivi des fuites de ressources
- ContrÎle de sécurité du flux
- Journalisation de chaque appel et de ses paramĂštres
- Suivi des appels Vulkan pour le profilage et la relecture
Voici un exemple de la façon dont une fonction pourrait ĂȘtre implĂ©mentĂ©e dans la couche de validation:
VkResult vkCreateInstance(
const VkInstanceCreateInfo* pCreateInfo,
const VkAllocationCallbacks* pAllocator,
VkInstance* instance) {
if (pCreateInfo == nullptr || instance == nullptr) {
log("Null pointer passed to required parameter!");
return VK_ERROR_INITIALIZATION_FAILED;
}
return real_vkCreateInstance(pCreateInfo, pAllocator, instance);
}
Vous pouvez combiner les couches de validation les unes avec les autres pour utiliser toutes les fonctionnalitĂ©s de dĂ©bogage dont vous avez besoin. En outre, les couches de validation peuvent ĂȘtre activĂ©es pour les versions de dĂ©bogage et complĂštement dĂ©sactivĂ©es pour les versions de version, ce qui est trĂšs pratique.
Vulkan n'a pas de couches de validation intégrées, mais Vulkan SDK de LunarG fournit un bon ensemble de couches pour suivre les bogues les plus courants. Toutes les couches sont open source et vous pouvez toujours voir quels bogues ils suivent. Grùce aux couches de validation, vous pouvez éviter les erreurs sur différents pilotes associées à un comportement indéfini.
Pour utiliser des couches de validation, elles doivent ĂȘtre installĂ©es sur le systĂšme. Par exemple, les couches de validation de LunarG ne sont disponibles que si le SDK Vulkan est installĂ©.
Auparavant, Vulkan avait deux types de couches de validation: spĂ©cifique Ă l'instance et spĂ©cifique Ă l'appareil. L'essentiel est que les couches d'instance vĂ©rifient les appels liĂ©s aux objets Vulkan globaux, tandis que les couches de pĂ©riphĂ©riques ne vĂ©rifient que les appels liĂ©s Ă un GPU spĂ©cifique. Ă ce stade, les couches de pĂ©riphĂ©rique sont obsolĂštes, de sorte que les couches de validation d'instance sont appliquĂ©es Ă tous les appels Vulkan. La spĂ©cification recommande toujours l'inclusion de couches de validation au niveau du pĂ©riphĂ©rique, notamment pour assurer la compatibilitĂ©, qui est requise pour certaines implĂ©mentations. Nous spĂ©cifierons les mĂȘmes couches pour l'instance et le pĂ©riphĂ©rique logique, dont nous parlerons un peu plus tard.
Utilisation des couches de validation
Dans cette section, nous verrons comment connecter les couches fournies par le SDK Vulkan. Tout comme pour les extensions, il faut spécifier les noms des couches pour les connecter. Tous les contrÎles qui nous sont utiles sont rassemblés dans une couche nommée "
VK_LAYER_KHRONOS_validation
".
Ajoutons deux constantes de configuration. Le premier (validationLayers) listera les couches de validation que nous voulons inclure. Le second (enableValidationLayers) permettra la connexion en fonction du mode de construction. Cette macro
NDEBUG
fait partie du standard C ++ et signifie «pas de débogage».
const uint32_t WIDTH = 800;
const uint32_t HEIGHT = 600;
const std::vector<const char*> validationLayers = {
"VK_LAYER_KHRONOS_validation"
};
#ifdef NDEBUG
const bool enableValidationLayers = false;
#else
const bool enableValidationLayers = true;
#endif
Ajoutons une nouvelle fonction
checkValidationLayerSupport
qui vérifiera si toutes les couches requises sont disponibles. Tout d'abord, obtenons une liste des couches disponibles en utilisant
vkEnumerateInstanceLayerProperties
. Son utilisation est similaire Ă la fonction que
vkEnumerateInstanceExtensionProperties
nous avons examinée précédemment.
bool checkValidationLayerSupport() {
uint32_t layerCount;
vkEnumerateInstanceLayerProperties(&layerCount, nullptr);
std::vector<VkLayerProperties> availableLayers(layerCount);
vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data());
return false;
AprÚs cela, vérifiez si tous les calques de
validationLayers
sont présents dans
availableLayers
. Vous devrez peut-ĂȘtre vous connecter
<cstring>
pour
strcmp
.
for (const char* layerName : validationLayers) {
bool layerFound = false;
for (const auto& layerProperties : availableLayers) {
if (strcmp(layerName, layerProperties.layerName) == 0) {
layerFound = true;
break;
}
}
if (!layerFound) {
return false;
}
}
return true;
La fonction peut maintenant ĂȘtre utilisĂ©e dans
createInstance
:
void createInstance() {
if (enableValidationLayers && !checkValidationLayerSupport()) {
throw std::runtime_error("validation layers requested, but not available!");
}
...
}
Exécutez le programme en mode débogage et assurez-vous qu'il n'y a pas d'erreurs.
Dans la structure,
VkInstanceCreateInfo
spécifiez les noms des couches de validation connectées:
if (enableValidationLayers) {
createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
createInfo.ppEnabledLayerNames = validationLayers.data();
} else {
createInfo.enabledLayerCount = 0;
}
Si notre vérification a été réussie,
vkCreateInstance
elle ne doit pas renvoyer d'erreur
VK_ERROR_LAYER_NOT_PRESENT
, mais il est préférable de la vérifier en exécutant le programme.
Interception des messages de débogage
Par dĂ©faut, les couches de validation envoient des messages de dĂ©bogage Ă la sortie standard, mais vous pouvez les gĂ©rer vous-mĂȘme en fournissant une fonction de rappel. Cela vous permettra de filtrer les messages que vous souhaitez recevoir, car tous ne contiennent pas d'avertissements d'erreur. Si vous souhaitez ignorer cette Ă©tape, passez directement Ă la derniĂšre section du chapitre.
Pour connecter une fonction de rappel afin de traiter les messages, vous devez configurer un messager de débogage à l'aide de
VK_EXT_debug_utils
.
Tout d'abord, ajoutons une fonction
getRequiredExtensions
qui retournera la liste des extensions requises selon que les couches de validation sont connectées ou non.
std::vector<const char*> getRequiredExtensions() {
uint32_t glfwExtensionCount = 0;
const char** glfwExtensions;
glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
std::vector<const char*> extensions(glfwExtensions, glfwExtensions + glfwExtensionCount);
if (enableValidationLayers) {
extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
}
return extensions;
}
Les extensions GLFW sont requises et l'extension de messagerie de débogage est ajoutée en fonction des conditions. Veuillez noter que nous utilisons une macro
VK_EXT_DEBUG_UTILS_EXTENSION_NAME
pour Ă©viter les fautes de frappe.
Nous pouvons maintenant utiliser cette fonction dans
createInstance
:
auto extensions = getRequiredExtensions();
createInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size());
createInfo.ppEnabledExtensionNames = extensions.data();
Exécutez le programme pour vérifier si nous avons reçu une erreur
VK_ERROR_EXTENSION_NOT_PRESENT
.
Voyons maintenant ce qu'est la fonction de rappel elle-mĂȘme. Ajoutons une nouvelle mĂ©thode statique avec un prototype
PFN_vkDebugUtilsMessengerCallbackEXT
.
VKAPI_ATTR
et
VKAPI_CALL
assurez-vous que la méthode a la signature correcte.
static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(
VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
VkDebugUtilsMessageTypeFlagsEXT messageType,
const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
void* pUserData) {
std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl;
return VK_FALSE;
}
Le premier paramÚtre détermine la gravité des messages, qui sont:
VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT
: message de diagnosticVK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT
: message d'information, par exemple sur la création d'une ressourceVK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT
: un message sur un comportement qui n'est pas nécessairement incorrect, mais qui indique trÚs probablement une erreurVK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT
: message concernant un comportement incorrect pouvant entraĂźner un plantage
Les valeurs d'énumération sont choisies de telle maniÚre que vous pouvez utiliser l'opération de comparaison pour filtrer les messages au-dessus ou en dessous d'un certain seuil, par exemple:
if (messageSeverity >= VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) {
// Message is important enough to show
}
Le paramĂštre
messageType
peut avoir les valeurs suivantes:
VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT
: l'événement qui s'est produit n'est pas lié aux spécifications ou aux performancesVK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT
: l'événement survenu enfreint la spécification ou indique une erreur possibleVK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT
: Vulkan ne peut pas ĂȘtre utilisĂ© de maniĂšre optimale
Le paramĂštre
pCallbackData
fait référence à une structure
VkDebugUtilsMessengerCallbackDataEXT
qui contient les détails du message. Les membres les plus importants de la structure sont:
pMessage
: message de débogage sous forme de chaßne terminée par nullpObjects
: un tableau de descripteurs d'objets liés au messageobjectCount
: nombre d'objets dans le tableau
Le paramĂštre
pUserData
contient le pointeur passé lors de la configuration de la fonction de rappel.
La fonction de rappel renvoie un
VkBool32
type. Le résultat indique s'il faut mettre fin à l'appel qui a généré le message. Si la fonction de rappel retourne
VK_TRUE
, l'appel est abandonné et un code d'erreur est renvoyé
VK_ERROR_VALIDATION_FAILED_EXT
. En rĂšgle gĂ©nĂ©rale, cela ne se produit que lors du test des couches de validation elles-mĂȘmes.Dans notre cas, vous devez revenir
VK_FALSE
.
Il reste Ă informer Vulkan de la fonction de rappel. Ătonnamment, mĂȘme le contrĂŽle d'une fonction de rappel de dĂ©bogage dans Vulkan nĂ©cessite un handle, qui doit ĂȘtre explicitement crĂ©Ă© et dĂ©truit. Cette fonction de rappel fait partie du messager de dĂ©bogage, et leur nombre est illimitĂ©. Ajoutez un membre de classe pour le descripteur aprĂšs
instance
:
VkDebugUtilsMessengerEXT debugMessenger;
Maintenant, ajoutez une fonction
setupDebugMessenger
qui sera appelée
initVulkan
juste aprĂšs
createInstance
:
void initVulkan() {
createInstance();
setupDebugMessenger();
}
void setupDebugMessenger() {
if (!enableValidationLayers) return;
}
Nous devons remplir la structure avec des détails sur le messager et ses fonctions de rappel:
VkDebugUtilsMessengerCreateInfoEXT createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
createInfo.pfnUserCallback = debugCallback;
createInfo.pUserData = nullptr; // Optional
Le champ
messageSeverity
vous permet de spécifier la gravité pour laquelle la fonction de rappel sera appelée. Nous définissons tous les degrés, sauf
VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT
pour ĂȘtre averti des problĂšmes Ă©ventuels et ne pas encombrer la console avec des informations de dĂ©bogage dĂ©taillĂ©es.
De mĂȘme, le champ
messageType
vous permet de filtrer les messages par type. Nous avons sélectionné tous les types, mais vous pouvez toujours désactiver ceux qui ne sont pas nécessaires.
Un
pfnUserCallback
pointeur vers la fonction de rappel est passé au champ . En option, vous pouvez passer un pointeur vers le champ
pUserData
, il sera passé à la fonction de rappel via un paramÚtre
pUserData
.
Notez qu'il existe d'autres façons de personnaliser les messages de couche de validation et les rappels de débogage, mais c'est la meilleure façon de démarrer avec Vulkan. Pour plus d'informations sur les autres méthodes, reportez-vous à la spécification d'extension .
La structure doit ĂȘtre transmise Ă la fonction
vkCreateDebugutilsMessengerEXT
pour créer l'objet
VkDebugUtilsMessengerEXT
. Il s'agit d'une fonctionnalitĂ© d'extension, elle n'est donc pas chargĂ©e automatiquement. Vous devez trouver vous-mĂȘme son adresse en utilisant
vkGetInstanceProcAddr
. Nous créerons notre propre fonction proxy qui le fera en interne. Ajoutez-le avant la définition de classe
HelloTriangleApplication
.
VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) {
auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT");
if (func != nullptr) {
return func(instance, pCreateInfo, pAllocator, pDebugMessenger);
} else {
return VK_ERROR_EXTENSION_NOT_PRESENT;
}
}
Nous utilisons cette fonction pour créer un messager:
if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) {
throw std::runtime_error("failed to set up debug messenger!");
}
L'avant-dernier paramÚtre est optionnel, c'est la fonction de rappel de l'allocateur, que nous spécifierons comme
nullptr
. Le reste des paramĂštres est assez simple. Puisque le messager est utilisĂ© pour une instance Vulkan spĂ©cifique (et ses couches de validation), un pointeur vers cette instance doit ĂȘtre passĂ© comme premier argument. Nous rencontrerons ce modĂšle pour d'autres objets enfants.
L'objet
VkDebugUtilsMessengerEXT
doit ĂȘtre dĂ©truit en appelant
vkDestroyDebugUtilsMessengerEXT
. De mĂȘme que pour
vkCreateDebugUtilsMessengerEXT
, nous devons charger cette fonction explicitement. Créez
ensuite
CreateDebugUtilsMessengerEXT
une autre fonction proxy:
void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) {
auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT");
if (func != nullptr) {
func(instance, debugMessenger, pAllocator);
}
}
VĂ©rifiez que cette fonction est soit une fonction statique de la classe, soit une fonction extĂ©rieure Ă la classe. AprĂšs cela, il peut ĂȘtre appelĂ© dans une fonction
cleanup
:
void cleanup() {
if (enableValidationLayers) {
DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr);
}
vkDestroyInstance(instance, nullptr);
glfwDestroyWindow(window);
glfwTerminate();
}
Instance de débogage de Vulkan
Nous avons ajouté le débogage avec des couches de validation, mais il y en a encore un peu plus. Une
vkCreateDebugUtilsMessengerEXT
instance valide est
vkDestroyDebugUtilsMessengerEXT
requise pour appeler et doit ĂȘtre appelĂ©e avant que l'instance ne soit dĂ©truite. Par consĂ©quent, nous ne pouvons pas dĂ©boguer
vkCreateInstance
et encore
vkDestroyInstance
.
Cependant, si vous lisez attentivement la spécification , vous verrez qu'il est possible de créer un messager de débogage distinct pour ces deux fonctions. Pour ce faire, vous devez définir le pointeur de
pNext
structure
VkInstanceCreateInfo
sur la structure
VkDebugUtilsMessengerCreateInfoEXT
. Commençons par déplacer le remplissage
VkDebugUtilsMessengerCreateInfoEXT
dans une méthode distincte:
void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) {
createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
createInfo.pfnUserCallback = debugCallback;
}
...
void setupDebugMessenger() {
if (!enableValidationLayers) return;
VkDebugUtilsMessengerCreateInfoEXT createInfo;
populateDebugMessengerCreateInfo(createInfo);
if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) {
throw std::runtime_error("failed to set up debug messenger!");
}
}
On peut le réutiliser dans une fonction
createInstance
:
void createInstance() {
...
VkInstanceCreateInfo createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
createInfo.pApplicationInfo = &appInfo;
...
VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo;
if (enableValidationLayers) {
createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
createInfo.ppEnabledLayerNames = validationLayers.data();
populateDebugMessengerCreateInfo(debugCreateInfo);
createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo;
} else {
createInfo.enabledLayerCount = 0;
createInfo.pNext = nullptr;
}
if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) {
throw std::runtime_error("failed to create instance!");
}
}
La variable
debugCreateInfo
est en dehors de l'instruction if afin qu'elle ne soit pas dĂ©truite avant d'ĂȘtre appelĂ©e
vkCreateInstance
. Créer un messager de débogage supplémentaire de cette maniÚre vous permet de l'utiliser automatiquement dans
vkCreateInstance
et
vkDestroyInstance
, aprÚs quoi il sera détruit.
Essai
Faisons délibérément une erreur pour voir les couches de validation en action.
Supprimez temporairement l'appel
DestroyDebugUtilsMessengerEXT
dans la fonction
cleanup
et exécutez le programme. Vous devriez vous retrouver avec quelque chose comme ceci:
Pour savoir quel appel a abouti Ă l'envoi du message, ajoutez un point d'arrĂȘt Ă la fonction de rappel du message et regardez la pile d'appels.
RĂ©glages
Il existe de nombreuses autres personnalisations qui régissent le comportement des niveaux de validation au-delà de ceux spécifiés dans la structure
VkDebugUtilsMessengerCreateInfoEXT
. Accédez au SDK Vulkan et ouvrez le répertoire
Config
. Vous y trouverez un fichier
vk_layer_settings.txt
expliquant comment configurer les calques.
Pour mettre en place les couches, copiez le fichier dans le répertoire
Debug
et
Release
puis suivez les instructions pour configurer le comportement souhaité. Cependant, dans le reste de ce guide, on supposera que vous utilisez les paramÚtres par défaut.
à l'avenir, nous ferons délibérément des erreurs pour vous montrer à quel point il est pratique et efficace d'utiliser des couches de validation pour les suivre.
Code C ++