Detailed validation layer Vulkan

Hello everyone, the next will introduce Vulkan authentication layer.

Vulkan APIThe design of the core is to try to minimize the driver's overhead, so-called overhead is more than pointing rendering operation. One concrete manifestation is the default condition, Vulkan APIthe support is very limited error checking. Even if an incorrect value or traversing parameters must be passed as a null pointer, processing logic will not be clear, and directly lead to the collapse of abnormal behavior or undefined. The reason for this is because Vulkanthe requirements for each step are very clearly defined, resulting in a small error is likely to cause, for example, to use the new GPUfeatures, but it is time to forget the request to create a logic device.

First, what is the validation layer

VulkanWe launched an optimized system that is called Validation layers. Validation layersIs an optional component, can be mounted to the Vulkanfunction call to the callback other operations. Validation layersThe common operating scenarios are:

  • The spec check parameter values, where final confirmation of whether or not there are inconsistent with expectations.
  • Creating and destroying the tracking target, to find whether there is leakage of resources.
  • Call chain tracking thread, a thread of execution to confirm the safety process.
  • The parameters to each function call record output is used in the standard, Vulkan preliminary profiling.

The following sample code is a function of applied Validation layersconcrete realization:

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);
}

These Validation layersare free to stack Vulkan driver, if necessary, you can even include all of the debugfeatures. You can simply open Validation layersthe debugversion and the releasefull version of the ban, so as to provide two versions ideal for you.

VulkanNo built any Validation layers, but LunarG Vulkan SDKprovides a series of layers for the detection of conventional error exception. They are completely OpenSource , so you can detect the needs of the specific application you need Validation layers. Use Validation layersis the best way to avoid your application receives impact when unknown behavior, or even interrupted.

VulkanUse only been installed in the system context Validation layers. For example, LunarG Validation layersonly in the installation Vulkan SDKare available on the PC.

In the previous Vulkantwo different types of releases Validation layerswere applied  instance and  device specific. This design hopes instancelayer will verify and global Vulkanobjects (for example Instance) about the call, but device specificbeds are only relevant to a particular GPU verification call. device specificLayer has been abandoned, which means that instancelayer Validation layerswill be applied to all of the Vulkancalls. For compatibility reasons, it is recommended in the specification document device specificopen layer Validation layers, which in some situations is necessary. We will logic devicespecify the layer of instancethe same Validation layers, will see later.

Second, the use validation layer

We'll show you how to enable Vulkan SDKstandard diagnostic layer provided. Like extension, the need to open by specifying a specific name validation layers. SDK by requesting VK_LAYER_LUNARG_standard_validaction layer, to be open about the implicit diagnosis layers, thus avoiding to explicitly specify all clear diagnostic layer.

First, add two configuration variables in the program to specify enabled layers, and whether they are turned on. Our selection is based on whether the program to be compiled in debug mode. NDEBUG is the C ++ standard macro definition, stands for "no debugging."

const int WIDTH = 800;
const int HEIGHT = 600;

const std::vector<const char*> validationLayers = {
    "VK_LAYER_LUNARG_standard_validation"
};

#ifdef NDEBUG
    const bool enableValidationLayers = false;
#else
    const bool enableValidationLayers = true;
#endif

We'll add a new function checkValidationLayerSupportto detect all requests layersis available. First, using vkEnumerateInstanceLayerProperties function lists all the available layers. Its usage and vkEnumerateInstanceExtensionProperties same as discussed in the section Instance.

bool checkValidationLayerSupport() {
    uint32_t layerCount;
    vkEnumerateInstanceLayerProperties(&layerCount, nullptr);

    std::vector<VkLayerProperties> availableLayers(layerCount);
    vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data());

    return false;
}

Then check validationLayers all in layer exists in availableLayers list. We need to use strcmp introduced.

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;

Now we createInstanceuse the function:

void createInstance() {
    if (enableValidationLayers && !checkValidationLayerSupport()) {
        throw std::runtime_error("validation layers requested, but not available!");
    }

    ...
}

Now run the program in debug mode, and to ensure that the error does not occur. If an error occurs, to ensure proper installation Vulkan SDK. If there is no or almost no layersreporting, we recommend using the latest SDK, or to LunarG official for help, need to register an account.

Finally, the modified VkInstanceCreateInfo structure, which has been opened to fill the current context name set validation layers.

if (enableValidationLayers) {
    createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
    createInfo.ppEnabledLayerNames = validationLayers.data();
} else {
    createInfo.enabledLayerCount = 0;
}

If the check is successful, vkCreateInstance not return VK_ERROR_LAYER_NOT_PRESENT error, make sure the program runs correctly.

Third, the message callback

Sorry that simply turn validation layersis no help, because up to now there is no way to diagnostic information back to the application. To accept the message, we must set a callback, you need VK_EXT_debug_report extension.

We've added a getRequiredExtensions function, which will be based on whether to open the validation layerslist of extensions needed to return.

GLFW expansion always needed, but debug reportextensions are added compiled according to the conditions. At the same time we use VK_EXT_DEBUG_REPORT_EXTENSION_NAME macro definition, it is equivalent literal "VK_EXT_debug_report", the use of macros to avoid hard-coding.

We createInstance calling function:

auto extensions = getRequiredExtensions();
createInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size());
createInfo.ppEnabledExtensionNames = extensions.data();

Run the program to ensure that did not receive VK_ERROR_EXTENSION_NOT_PRESENT error message, we do not need to verify the existence of extensions, because it will be effective validation layersverification engine.

Now let's look at callbackthe way the function, add a static function debugCallback , and use PFN_vkDebugReportCallbackEXT  prototype modified. VKAPI_ATTR and VKAPI_CALL ensure the correct function signature, thereby Vulkan called.

static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(
    VkDebugReportFlagsEXT flags,
    VkDebugReportObjectTypeEXT objType,
    uint64_t obj,
    size_t location,
    int32_t code,
    const char* layerPrefix,
    const char* msg,
    void* userData) {

    std::cerr << "validation layer: " << msg << std::endl;

    return VK_FALSE;
}

The first argument specifies the type of message, it can flag any bit combination:

  • VK_DEBUG_REPORT_INFORMATION_BIT_EXT
  • VK_DEBUG_REPORT_WARNING_BIT_EXT
  • VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT
  • VK_DEBUG_REPORT_ERROR_BIT_EXT
  • VK_DEBUG_REPORT_DEBUG_BIT_EXT

objType parameter describes the type of objects as the message subject, such as a obj is VkPhysicalDevice , then objType is VK_DEBUG_REPORT_OBJECT_TYPE_DEVICE_EXT . It is allowed to do so because of internal handle Vulkan is defined as uint64_t . msg parameter contains a pointer pointing to the message. Finally, there is a userData parameter can be customized data callback.

Callback returns a Boolean value that indicates the trigger validation layermessage Vulkanwhether the call should be aborted. If it returns true, the call will be VK_ERROR_VALIDATION_FAILED_EXT error aborted. This is often used to test validation layersitself, so we always return VK_FALSE .

Now we need to tell Vulkanthe callback function on definitions. Perhaps you would be more surprised, even debug callback is also a clear need to create and destroy handle management. Add a callback handler class member store, in instance under.

VkDebugReportCallbackEXT callback;

Now add a function setupDebugCallback , the function will initVulkan function call createInstance called after.

void initVulkan() {
    createInstance();
    setupDebugCallback();
}

void setupDebugCallback() {
    if (!enableValidationLayers) return;

}

Now we are filling more information about the callback structure:

VkDebugReportCallbackCreateInfoEXT createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT;
createInfo.flags = - VK_DEBUG_REPORT_ERROR_BIT_EXT | - VK_DEBUG_REPORT_WARNING_BIT_EXT;
createInfo.pfnCallback = debugCallback;

Flag allows you to filter out unwanted messages. pfnCallback field describes pointer to callback function. Here can pass selectively a pUserData pointer, most custom callback data structure used, for example can pass HelloTriangleApplication pointer type.

This structure should be passed to vkCreateDebugReportCallbackEXT function to create VkDebugReportCallbackEXT object. Unfortunately, because this function is an extension, it will not be loaded automatically. So you must use vkGetInstanceProcAddr find the address of the function. We will create the proxy function in the background. In HelloTriangleApplication add it above class definition.

VkResult CreateDebugReportCallbackEXT(VkInstance instance, const VkDebugReportCallbackCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugReportCallbackEXT* pCallback) {
    auto func = (PFN_vkCreateDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT");
    if (func != nullptr) {
        return func(instance, pCreateInfo, pAllocator, pCallback);
    } else {
        return VK_ERROR_EXTENSION_NOT_PRESENT;
    }
}

If the function is not loaded, vkGetInstanceProcAddr function returns nullptr. If the non nullptr, you can call this function to create extended objects:

if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, &callback) != VK_SUCCESS) {
    throw std::runtime_error("failed to set up debug callback!");
}

The penultimate parameter is still dispenser callback pointer, we still set nullptr. debug callbacks Vulkan instanceand layerscorrespond, so the need to explicitly specify the first parameter. Now run the program, close the window, you'll see a message at the command line:
Validation Layer: Debug Report the callbacks not removed the before DestroyInstance
now Vulkan have found an error in the program by calling needs! VkDestroyDebugReportCallbackEXT clean up VkDebugReportCallbackEXT object. And vkCreateDebugReportCallbackEXT Similarly, the function requires explicit load. In CreateDebugReportCallbackEXT create another proxy function under.

void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT callback, const VkAllocationCallbacks* pAllocator) {
    auto func = (PFN_vkDestroyDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT");
    if (func != nullptr) {
        func(instance, callback, pAllocator);
    }
}

This function is defined as a static class function or an external function, we call the cleanup function:

void cleanup() {
    DestroyDebugReportCallbackEXT(instance, callback, nullptr);
    vkDestroyInstance(instance, nullptr);

    glfwDestroyWindow(window);

    glfwTerminate();
}

Run the program again, you will see the error message has disappeared. If you want to see which call triggers a message, you can add a breakpoint to the callback messages, and view the call stack chain.

Fourth, the configuration

Validation layersThe behavior may have more settings, not just VkDebugReportCallbackCreateInfoEXT structure specified in the flag information. Browse Vulkan SDKthe Config directory. Find vk_layer_settings.txt file, which has instructions on how to configure layers.

To configure your own application layers, save the file assigned to the project's Debug and Release directories, and then follow the instructions to set the desired functional properties. In addition, this tutorial will use the default settings.

Fifth, the sample code

// validation_layers.cpp
#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>

#include <iostream>
#include <stdexcept>
#include <vector>
#include <cstring>
#include <cstdlib>

const int WIDTH = 800;
const int HEIGHT = 600;

const std::vector<const char*> validationLayers = {
    "VK_LAYER_KHRONOS_validation"
};

#ifdef NDEBUG
const bool enableValidationLayers = false;
#else
const bool enableValidationLayers = true;
#endif

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

void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) {
    auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT");
    if (func != nullptr) {
        func(instance, debugMessenger, pAllocator);
    }
}

class HelloTriangleApplication {
public:
    void run() {
        initWindow();
        initVulkan();
        mainLoop();
        cleanup();
    }

private:
    GLFWwindow* window;

    VkInstance instance;
    VkDebugUtilsMessengerEXT debugMessenger;

    void initWindow() {
        glfwInit();

        glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
        glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);

        window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr);
    }

    void initVulkan() {
        createInstance();
        setupDebugMessenger();
    }

    void mainLoop() {
        while (!glfwWindowShouldClose(window)) {
            glfwPollEvents();
        }
    }

    void cleanup() {
        if (enableValidationLayers) {
            DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr);
        }

        vkDestroyInstance(instance, nullptr);

        glfwDestroyWindow(window);

        glfwTerminate();
    }

    void createInstance() {
        if (enableValidationLayers && !checkValidationLayerSupport()) {
            throw std::runtime_error("validation layers requested, but not available!");
        }

        VkApplicationInfo appInfo = {};
        appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
        appInfo.pApplicationName = "Hello Triangle";
        appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
        appInfo.pEngineName = "No Engine";
        appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
        appInfo.apiVersion = VK_API_VERSION_1_0;

        VkInstanceCreateInfo createInfo = {};
        createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
        createInfo.pApplicationInfo = &appInfo;

        auto extensions = getRequiredExtensions();
        createInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size());
        createInfo.ppEnabledExtensionNames = extensions.data();

        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!");
        }
    }

    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!");
        }
    }

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

    bool checkValidationLayerSupport() {
        uint32_t layerCount;
        vkEnumerateInstanceLayerProperties(&layerCount, nullptr);

        std::vector<VkLayerProperties> availableLayers(layerCount);
        vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data());

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

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

int main() {
    HelloTriangleApplication app;

    try {
        app.run();
    } catch (const std::exception& e) {
        std::cerr << e.what() << std::endl;
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}
C++

 

 

 

 

 

 

Published 51 original articles · won praise 60 · views 20000 +

Guess you like

Origin blog.csdn.net/u010281924/article/details/105367804