From 4d16ae606d8bf10da6007a5afa7d4916fcc6a982 Mon Sep 17 00:00:00 2001 From: Lillian Salehi Date: Sat, 5 Oct 2024 05:50:37 -0500 Subject: [PATCH] Majorly revamp the debug system, setting up a validation layer with a debug messenger, document it all, clean up main and VulkanDebugLibs --- src/debug/VulkanDebugLibs.cpp | 148 +++++++++++++++++++++++++++++++--- src/debug/VulkanDebugLibs.h | 6 +- src/main.cpp | 83 +++++++------------ 3 files changed, 169 insertions(+), 68 deletions(-) diff --git a/src/debug/VulkanDebugLibs.cpp b/src/debug/VulkanDebugLibs.cpp index 1c06439..b092d56 100644 --- a/src/debug/VulkanDebugLibs.cpp +++ b/src/debug/VulkanDebugLibs.cpp @@ -1,4 +1,7 @@ +#include #include +#include +#include #define GLFW_INCLUDE_VULKAN #include @@ -9,23 +12,104 @@ using namespace AgnosiaEngine; #include #include - const std::vector validationLayers = { - "VK_LAYER_KHRONOS_validation" - }; +// This ifdef checks if the build flag is present, hence whether to hook the debugger in at all. +#ifdef DEBUG + const bool enableValidationLayers = true; +#else + const bool enableValidationLayers = false; +#endif +// This is our messenger object! It handles passing along debug messages to the debug callback we will also set. +VkDebugUtilsMessengerEXT debugMessenger; +// This is the set of "layers" to hook into. Basically, layers are used to tell the messenger what data we want, its a filter. *validation* is the general blanket layer to cover incorrect usage. +const std::vector validationLayers = { + "VK_LAYER_KHRONOS_validation" +}; -void VulkanDebugLibs::vulkanDebugSetup(VkInstanceCreateInfo& createInfo) { - createInfo.enabledLayerCount = static_cast(validationLayers.size()); - createInfo.ppEnabledLayerNames = validationLayers.data(); +std::vector getRequiredExtensions() { + // This gets a little weird, Vulkan is platform agnostic, so you need to figure out what extensions to interface with the current system are needed + // So, to figure out what extension codes and how many to use, feed the pointer into *glfwGetRequiredInstanceExtensions*, which will get the necessary extensions! + // From there, we can send that over to our createInfo Vulkan info struct to make it fully platform agnostic! + uint32_t glfwExtensionCount = 0; + const char** glfwExtensions; + glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + + if(enableValidationLayers) { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + return extensions; } - -bool VulkanDebugLibs::checkValidationLayerSupport() { // This function is used to check Validation Layer Support, validation layers are the debug trace tools in the Vulkan SDK. - uint32_t layerCount; // layerCount will be used as the var to keep track of the number of requested validation layerk - vkEnumerateInstanceLayerProperties(&layerCount, nullptr); // Set layerCount to the number of validation layers requested when pProperties is NULLPTR - std::vector availableLayers(layerCount); // VkLayerProperties is a structure with data on the layername, desc, versions and etc. - vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data());// Now that we have a VkLayerProperties fed in, as well as the num. of properties, we can fill layerCount with the VkResult +static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback( + VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, + VkDebugUtilsMessageTypeFlagsEXT messageType, + const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, + void* pUserData) { + // One hell of a function, this is using the *PFN_vkDestroyDebugUtilsMessengerEXT* prototype, the prototype for an, "Application-defined debug messenger callback function". + // The VKAPI_CALL and VKAPI_ATTR ensure that the function has the right signature for vulkan to call it. The callback message can be anything from a diagnostic to error! + // You can even sort by those diagnostics with their flags, since they are just integers, maybe TODO? + std::cerr << "Validation layer: " << pCallbackData->pMessage << std::endl; - for(const char* layerName : validationLayers) { // Pretty straightforward from here, just enumerate over all the VkResult data and see if we have any validationLayers + return VK_FALSE; +} + +void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + // There is absolutely nothing about this i like, those long ass flags for messageType and Severity are just fucking hex values. Khronos should never cook again ToT + // On a serious note, this is just a struct to define the parameters of the debug messenger, nothing super special. + 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 +} + +void VulkanDebugLibs::vulkanDebugSetup(VkInstanceCreateInfo& createInfo, VkInstance& instance) { + // This function is quite useful, we first populate the debug create info structure, all the parameters dictating how the debug messenger will operate. + // The reason we populate the debug messenger so late is actually on purpose, we need to set the createInfo, which depends on the debugMessenger info, + // and if we set it before the creation of the instance, we cant debug vkCreateInstance or vkDestroyInstance! It's timed perfectly as of now. + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{}; + auto extensions = getRequiredExtensions(); + createInfo.enabledExtensionCount = static_cast(extensions.size()); + createInfo.ppEnabledExtensionNames = extensions.data(); + + if(enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(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!"); + } +} + +void VulkanDebugLibs::checkUnavailableValidationLayers() { + // Check if we are trying to hook validation layers in without support. + if(enableValidationLayers && !checkValidationLayerSupport()) { + throw std::runtime_error("Validation layers request, but not available! Are your SDK path variables set?"); + } +} + +bool VulkanDebugLibs::checkValidationLayerSupport() { + // This function is used to check Validation Layer Support, validation layers are the debug trace tools in the Vulkan SDK. + // layerCount will be used as the var to keep track of the number of requested validation layer + // VkLayerProperties is a structure with data on the layername, desc, versions and etc. + + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + + std::vector availableLayers(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + + for(const char* layerName : validationLayers) { bool layerFound = false; for(const auto& layerProperties : availableLayers) { @@ -42,5 +126,43 @@ bool VulkanDebugLibs::checkValidationLayerSupport() { // Thi return true; } +VkResult CreateDebugUtilsMessengerEXT( + VkInstance instance, + const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkDebugUtilsMessengerEXT* pDebugMessenger) { + // This function builds out debug messenger structure! + // It's a little odd, we have to look up the address of the vkCreateDebugUtilsMessengerEXT ourselves because its an extension function, + // therefore, not auto-loaded. + auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + if (func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); + } else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +void VulkanDebugLibs::DestroyDebugUtilsMessengerEXT(VkInstance instance, + const VkAllocationCallbacks* pAllocator) { + // We are doing kind of the same thing as before in the create function, find the address of the DestroyDebugUtils function, and call it. + + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + if(func != nullptr) { + func(instance, debugMessenger, pAllocator); + } +} + +void VulkanDebugLibs::setupDebugMessenger(VkInstance& vulkanInstance) { + // This is a pretty simple function! we just pass in the values to build the debug messenger, populate the structure with the data we want, + // and safely create it, covering for runtime errors as per usual, this is the first thing that will be called! + if(!enableValidationLayers) return; + + VkDebugUtilsMessengerCreateInfoEXT createInfo; + populateDebugMessengerCreateInfo(createInfo); + + if(CreateDebugUtilsMessengerEXT(vulkanInstance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { + throw std::runtime_error("Failed to set up the Debug Messenger!"); + } +} diff --git a/src/debug/VulkanDebugLibs.h b/src/debug/VulkanDebugLibs.h index 41799e9..8cc7a34 100644 --- a/src/debug/VulkanDebugLibs.h +++ b/src/debug/VulkanDebugLibs.h @@ -3,8 +3,10 @@ namespace AgnosiaEngine { class VulkanDebugLibs { public: - void vulkanDebugSetup(VkInstanceCreateInfo& createInfo); + void vulkanDebugSetup(VkInstanceCreateInfo& createInfo, VkInstance& instance); bool checkValidationLayerSupport(); + void checkUnavailableValidationLayers(); + void setupDebugMessenger(VkInstance& vulkanInstance); + void DestroyDebugUtilsMessengerEXT(VkInstance instance, const VkAllocationCallbacks* pAllocator); }; } - diff --git a/src/main.cpp b/src/main.cpp index 66c7dfd..53d3118 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,6 +1,6 @@ +#include #define GLFW_INCLUDE_VULKAN -#include #include #include "debug/VulkanDebugLibs.h" @@ -10,20 +10,21 @@ using namespace AgnosiaEngine; #include #include #include -#include + +#ifdef DEBUG + const bool enableValidationLayers = true; +#else + const bool enableValidationLayers = false; +#endif + +const uint32_t WIDTH = 800; +const uint32_t HEIGHT = 600; // Define a base class structure to handle public and private methods class TriangleTestApplication { - const uint32_t WIDTH = 800; - const uint32_t HEIGHT = 600; - - #ifdef DEBUG - const bool enableValidationLayers = true; - #else - const bool enableValidationLayers = false; - #endif public: + void run() { initWindow(); initVulkan(); @@ -32,9 +33,9 @@ public: } private: - GLFWwindow *window; + GLFWwindow* window; VkInstance instance; - + VulkanDebugLibs debug; // Initialize GLFW Window. First, Initialize GLFW lib, disable resizing for // now, and create window. void initWindow() { @@ -48,63 +49,39 @@ private: void initVulkan() { createInstance(); + debug.setupDebugMessenger(instance); // The debug messenger is out holy grail, it gives us Vulkan related debug info when built with the -DNDEBUG flag (as per the makefile) } void createInstance() { - VulkanDebugLibs debug; - - if(enableValidationLayers && !debug.checkValidationLayerSupport()) { - throw std::runtime_error("Validation layers requested, but not available!"); - } + debug.checkUnavailableValidationLayers(); // Check if there is a mistake with our Validation Layers. // Set application info for the vulkan instance! VkApplicationInfo appInfo{}; - appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; // Tell vulkan that appInfo is a Application Info structure - appInfo.pApplicationName = "Triangle Test"; // Give the struct a name to use - appInfo.applicationVersion = VK_MAKE_VERSION(1,0,0); // Create a Major Minor Patch version number for the application! - appInfo.pEngineName = "Agnosia Engine"; // Give an internal name for the engine running - appInfo.engineVersion = VK_MAKE_VERSION(1,0,0); // Similar to the App version, give vulkan an *engine* version - appInfo.apiVersion = VK_API_VERSION_1_0; // Tell vulkan what the highest API version we will allow this program to run on + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; // Tell vulkan that appInfo is a Application Info structure + appInfo.pApplicationName = "Triangle Test"; // Give the struct a name to use + appInfo.applicationVersion = VK_MAKE_VERSION(1,0,0); // Create a Major Minor Patch version number for the application! + appInfo.pEngineName = "Agnosia Engine"; // Give an internal name for the engine running + appInfo.engineVersion = VK_MAKE_VERSION(1,0,0); // Similar to the App version, give vulkan an *engine* version + appInfo.apiVersion = VK_API_VERSION_1_0; // Tell vulkan what the highest API version we will allow this program to run on - VkInstanceCreateInfo createInfo{}; // Define parameters of new vulkan instance - createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; // Tell vulkan this is a info structure - createInfo.pApplicationInfo = &appInfo; // We just created a new appInfo structure, so we pass the pointer to it. - - // This gets a little weird, Vulkan is platform agnostic, so you need to figure out what extensions to interface with the current system are needed - // So, to figure out what extension codes and how many to use, feed the pointer into *glfwGetRequiredInstanceExtensions*, which will get the necessary extensions! - // From there, we can send that over to our createInfo Vulkan info struct to make it fully platform agnostic! - uint32_t glfwExtensionCount = 0; - const char** glfwExtensions; + VkInstanceCreateInfo createInfo{}; // Define parameters of new vulkan instance + createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; // Tell vulkan this is a info structure + createInfo.pApplicationInfo = &appInfo; // We just created a new appInfo structure, so we pass the pointer to it. - glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - - createInfo.enabledExtensionCount = glfwExtensionCount; - createInfo.ppEnabledExtensionNames = glfwExtensions; - - if(enableValidationLayers) { // If we have validation layers, add them now, otherwise set it to 0 - debug.vulkanDebugSetup(createInfo); - } else { - createInfo.enabledLayerCount = 0; - } - VkResult result = vkCreateInstance(&createInfo, nullptr, &instance); // Finally create the Vulkan instance, passing in the info to create from, and the global instance to use! - - if(result != VK_SUCCESS) { // vkCreateInstance returns a VkResult, if its anything but success, we exit immediately. (VK_SUCCESS == 0) - throw std::runtime_error("Failed to create vulkan instance!"); - } + debug.vulkanDebugSetup(createInfo, instance); // Handoff to the debug library to wrap the validation libs in! (And set the window up!) } - - - void mainLoop() { - // Update window whilst open + void mainLoop() { // This loop just updates the GLFW window. while (!glfwWindowShouldClose(window)) { glfwPollEvents(); } } - void cleanup() { - // Cleanup window when destroyed. + void cleanup() { // Similar to the last handoff, destroy the debug util in a safe manner in the library! + if(enableValidationLayers) { + debug.DestroyDebugUtilsMessengerEXT(instance, nullptr); + } vkDestroyInstance(instance, nullptr); glfwDestroyWindow(window); glfwTerminate();