Vulkan. Guide du développeur. Couches de validation

Je suis un traducteur de CG Tribe Ă  Izhevsk, et ici je partage la traduction du manuel de l'API Vulkan. Lien source - vulkan-tutorial.com .



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
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







Couches de validation







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 diagnostic
  • VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT



    : message d'information, par exemple sur la création d'une ressource
  • VK_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 erreur
  • VK_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 performances
  • VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT



    : l'événement survenu enfreint la spécification ou indique une erreur possible
  • VK_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 null
  • pObjects



    : un tableau de descripteurs d'objets liés au message
  • objectCount



    : 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 ++



All Articles