[vk] Drawing triangle - setup - Validation layers

2021. 8. 5. 04:48그래픽스/vk

What are validation layers?

- Vulkan API 는 드라이버 오버 헤드를 최소화하자는 목표를 바탕으로 설계됨.

- 이 목표중 하나는 API에서 기본적으로 오류검사를 하지않는다는것

- 간단한 실수조차 명시적으로 처리 x => will simply result in crashes or undefined behavior

- vulkan은 모든것에대해 명시해줘야하기 때문에 실수하기 쉬움

- 이러한 검사를 API에 추가할 수 있음

 

"validation layers"

- 우아한 vulkan의 멋진 시스템

- 유효성 검사 계층은 선택적 구성요소

- Vulkan 함수를 후킹하여 추가적인 동작을 수행하도록함

- 일반적인 operations은 다음과 같다

  • Checking the values of parameters against the specification to detect misuse (스펙에 대한 파라미터값 검사)
  • Tracking creation and destruction of objects to find resource leaks (누출된 리소스-객체생성과 파괴추적)
  • Checking thread safety by tracking the threads that calls originate from (스레드 추적, 스레드안전성)
  • Logging every call and its parameters to the standard output (모든 호출, 매개변수를 기록)
  • Tracing Vulkan calls for profiling and replaying (프로파일링, 리플레이)

아래는 진단을 위한 유효성 감사 레이어를 적용한 함수 구현을 보여주는 예시

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

 

=> 원하는 모든 디버깅 기능을 포함할 수 있도록, 원하는만큼 중첩가능 (freely stacked)

=> 디버그 빌드에서는 사용하도록 설정하고, 릴리스 빌드에 대해서는 유효성 검사 레이어를 완전히 비활성화

=> gives you the best of both worlds!

 

LunarG Vulkan SDK

= 일반적인 오류를 검사하는 멋진 레이어 세트를 제공.

= 완전한 open source

 

유효성 검사 레이어

= 가장 좋은 어플리케이션이 중단되는것을 방지하는 방법

= 다른 드라이버에서의 우연한 undefined behavior 방지 

= 설치된 경우만 사용가능 (the LunarG validation layers은 Vulkan sdk 가 설치된 PC만)

 

두가지 유형의 유효성 검사 레이어

- instance layer , device specific layer

- instance layer : instance같은 Vulkan 전역 객체들과 관련된 호출만

- device specific layer : specific GPU 와 연관된 호출만 확인, 더이상 사용하지 않음 (deprecated)

- 인스턴스 유효성검사 계층이 모든 vulkan 호출에 적용됨.

- 스펙 문서에서는 일부 구현에서 요구되는 호환성을 위해 장치 수준에서 유효성 검사를 권장.

(논리적 장치 레벨에서 인스턴스와 동일한 레이어를 지정하기만 하면됨)

 

 

1. Using validation layers

- Vulkan SDK에서 제공하는 표준 진단 레이어를 활성화 하는 방법을 설명함

- 확장 기능과 마찬가지로 유효성 검사 레이어는 이름을 지정하여 활성화 해야함, (be enable by specifying)

- 모든 유용한 표준 레이어를 VK_LAYER_KHRONOS_validation 로 알려진 SDK를 사용하여, 포함시킬 수 있음

(All of the useful standard validation is bundled into a layer included in the SDK that is known as VK_LAYER_KHRONOS_validation.)

 

두개의 구성 변수를 프로그램에 추가하여, 활성화 할 레이어와 활성화 여부를 지정할 수 있음

- NDEBUG 매크로는 C++ 표준으로, 디버그하지 않음을 의미함.

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

 

checkValidationLayerSupport 함수

- 요청된 모든 레이어가 사용가능한지 확인하는 새함를 추가

- vkEnumerateInstanceLayerProperties 함수를 사용하여 사용 가능한 모든 확장을 나열

- 그 사용법은 인스턴스의 생성에 대한 글에서 논의된 vkEnumerateInstanceExtensionProperties의 사용법과 동일

availableLayers.data() : 벡터의 첫 데이터의 주소를 리턴함.
strcmp 함수 : <cstring> 헤더파일 

 

    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) {    // 하나라도 찾지 못하면 false
                return false;
            }
        }

        return true;   // 모두 이용 가능해야한다.
    }

createInstance에서 이 함수를 사용 => 레이어 검증

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

    ...
}

=> 오류가 있을 시 여기에  문제를 제출(계정필요)

 

마지막으로 VkInstanceCreateInfo구조체 인스턴스를 수정해야함

- 유효성 레이어 이름을 포함시켜야함,

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

=> vkCreateInstance 는 VK_ERROR_LAYER_NOT_PRESENT 오류를 반환하지 않아야함.

=> 프로그램을 실행하여 이를 확인해야함.

 

 

 

2. Message callback

- 기본적으로 standard output으로 디버그메시지를 출력한다

- 하지만 이러한 메시지를 받으려면, 콜백함수를 명시하여 제공해줘야함

- 이것은 메시지 종류를 선택할 수 있게 해준다(모든 에러 메시지가 필요없기때문?)

(This will also allow you to decide which kind of messages you would like to see, because not all are necessarily (fatal) errors)

(지금 당장 이런 기능을 원하지 않으면 스킵해도 상관없다)

 

Callback

- 메시지를 다루기위해 설정해야함

- handle message, associated details 등등 을 파악하기위해

- debug messenger를 VK_EXT_debug_utils extension.을 사용하여 set up 해야함.

 

 

2.1 getRequiredExtensions

- 유효성 검사 레이어의 사용 가능 여부에 따라 필요한 확장 프로그램 목록을 반환하는 함수

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

- glfw로 지정된 확장기능은 항상 필요, 하지맘 디버그 보고서 확장기능은 디버그할때만사용하므로.

  조건부로 추가해야함

- 매크로를 사용하여, 오타를 피할 수 있음

 

createInstance

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

- 프로그램을 실행하여 VK_ERROR_EXTENSION_NOT_PRESENT 오류가 발생하지 않도록해야함.

- 이 확장 기능의 존재여부는 확인할 필요없음,(유효성 검사레이어를 사용한다는것은 존재한다는것을 암시)

 

 

2.2 debugCallback

- 오류메시지를 출력하는 콜백함수를 일단 구현해보자.

- debugcallback이라는 static 멤버함수PFN_vkDebugUtilsMessengerCallbackEXT 프로토타입과 함께추가

- VKAPI_ATTR and VKAPI_CALL : Vulkan이 함수호출하는데있어서 올바른 서명을 갖고있는지 보증

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

- 첫번째 매개 변수 : 메시지의 심각도 the severity of the message, 아래 플래그중 하나를 따라야함.

  • VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT: Diagnostic message
  • VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT: Informational message like the creation of a resource
  • VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT: Message about behavior that is not necessarily an error, but very likely a bug in your application
  • VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT: Message about behavior that is invalid and may cause crashes

 enumeration 값은 다음과 같이 비교연산을 통해 메시지의 레벨정도를 비교할 수 있음.

if (messageSeverity >= VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) {
    // Message is important enough to show
}

- 두번째 매개변수 messageType 은 아래와 같은 값을 가짐

  • VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT: Some event has happened that is unrelated to the specification or performance
  • VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT: Something has happened that violates the specification or indicates a possible mistake
  • VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT: Potential non-optimal use of Vulkan

- 세번째 매개변수 pCallbackDataVkDebugUtilsMessengerCallbackDataEXT 구조체를 참조하며, 메시지 자체의 디테일을 나타냄, 이 구조체의 중요 멤버변수는 다음과 같다.

  • pMessage: The debug message as a null-terminated string
  • pObjects: Array of Vulkan object handles related to the message
  • objectCount: Number of objects in array

- 마지막 매개변수 pUserData  는 콜백을 설정하는동안 이 함수에 넘겨줄 데이터

 

- 이 콜벡함수는 boolean값을 반환한다. 

- 이 값에 따라 이 콜백함수를 트리거한 vulkan 호출을 중단해야하는지를 결정한다.

- 만일 callback이 true값을 리턴한다면, 호출은 abort 되며 VK_ERROR_VALIDATION_FAILED_EXT error가 발생

- true는 유효성 검사 레이어 자체를 테스트하는데만 사용됨

-  so you should always return VK_FALSE.

 

 

2.3 CallbackFunction 등록

- Vulkan의 디버그 콜백조차 명시적으로 생성 및 소멸되어야하는 핸들로 관리해야함

- 이러한 콜백은 디버그 메신저의 일부이며, 원하는 만큼 가질 수 있음

- 이 핸들에 대한 클래스 멤버를 추가해야함. 

VkDebugUtilsMessengerEXT debugMessenger;

- setupDebugMessenger 이라는 함수를 추가하여 위 멤버변수를 초기화해야함. 여기선 인스턴스 바로 다음에 초기화

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

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

}

이제 이 함수 내부에서, 이 구조체의 멤버들을 등록해줘야함. 

- 위에 debugCallback의 매개변수인 messageSeverity 와 messageType, pUserdata 를 볼 수 있음.

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

messageSeverity

- 콜백을 호출하려는 모든 유형의 심각도를 지정할 수 있음,

- 리소스 생성과 관련된 VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT 를 제외한

- 모든 문제를 수신하기위해 모든 타입을 지정한것. (리소스 생성관련은 메시지양이 많음)

messageType

- 콜백에 알림을 받는 메시지 유형을 필터링

- 모든 유형을 단순히 활성화, 필요없으면 비활성화

pfnUserCallbac

- 콜백함수에 대한 포인터를 지정함.

pUserData

- 콜백함수에 전달될 포인터를 선택적으로 전달할 수 있음

-  예를 들어 지금 만들고 있는 HelloTriangleApplication 클래스에 대한 포인터를 넘겨줄 수 있음

 

2.4 vkCreateDebugUtilsMessengerEXT

- 디버그 메신저를 생성하기 위한 함수

- 위 구조체를 넘겨줘야함

- 이 함수는 확장함수(extension function)

- 자동적으로 load하지 못함

- 다음과 같은 함수로 주소를 찾아 사용할수있음. =>  vkGetInstanceProcAddr 

- 이를 처릴 할 프록시 함수를 만들어 사용하는게 편함.

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

- instance + create info + pAllocator + pDebugMessenger

vkGetInstanceProcAddr

- 함수를 로드하지 못하면 nullptr를 리턴하게된다.

- 이 함수를 호출하여 객체를 만들 수 있음

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

- 마지막 두번째 매개변수는 nullptr (pAllocator)

- 디버그 메신저는 Vulkan 인스턴스와 해당 계층에 고유함,

- 적용할 인스턴스를 명시적으로 지정 해야함.

- 나중에 다른 자식 객체에서도 이 패턴을 볼 수 있음. (child objects)

 

 

VkDebugUtilsMessengerEXT

- vkDestroyDebugUtilsMessengerEXT.호출로 객체도 정리해야함

- vkCreateDebugUtilsMessengerEXT 함수와 유사하게 주소를 사용하여 프록시함수 작성

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

- 이 함수가 static class funtion 인지 function outside the class 인지 확실히해야함

- cleanup 멤버 함수에서 호출하여 제거해야한다. (인스턴스 제거보다 위에 있음) 

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

    vkDestroyInstance(instance, nullptr);

    glfwDestroyWindow(window);

    glfwTerminate();
}

 

 

3. Debugging instance creation and destruction

- 유효성 검사 레이어를 사용한 디버깅 프로그램을 추가했지만

- 이 메신저가 만들어지기 전과, destroy 된 이후는 확인하지못함

  ( vkCreateInstance vkDestroyInstance )

- 이 두 함수 호출을 위해 특별히 별도의 디버그 유틸리티 메신저를 만들어야함

- VkInstanceCreateInfo 의 확장 필드인 pNext에 VkDebugUtilsMessengerCreateInfoEXT 포인터를전달해야함.

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

-> 코드의 중복을 피하기위해 따로 함수를 만든뒤 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!");
    }
}

debugCreateInfo

 

-  vkCreateInstance 호출이전에 파괴되지 않도록 if문 외부에 배치하여 

- 이 방법으로 추가 디버그 메신저를 작성하여 vkCreateInstance and vkDestroyInstance 에도 디버깅 가능.

 

Testing

- cleanup 함수에서 DestroyDebugUtilsMessengerEXT 를 제거하여 실행하면 다음과 같은 에러 메시지가 뜬다.

If you don't see any messages then check your installation.

- If you want to see which call triggered a message,

- you can add a breakpoint to the message callback and look at the stack trace.

 

 

Configuration

- VkDebugUtilsMessengerCreateInfoEXT구조체에 지정된 플래그보다 유효성검증레이어에서 더많은 행동을한다.

- Vulkan SDK 에서 Config directory 를 찾아보면 vk_layer_settings.txt 이 있을것이다. 

- 이 파일에 어떻게 레이어를 설정하는지 방법을 찾을 수 있음.

 

There are a lot more settings for the behavior of validation layers than just the flags specified in the VkDebugUtilsMessengerCreateInfoEXT struct. Browse to the Vulkan SDK and go to the Config directory. There you will find a vk_layer_settings.txt file that explains how to configure the layers.

 

To configure the layer settings for your own application, copy the file to the Debug and Release directories of your project and follow the instructions to set the desired behavior. However, for the remainder of this tutorial I'll assume that you're using the default settings.

 

Throughout this tutorial I'll be making a couple of intentional mistakes to show you how helpful the validation layers are with catching them and to teach you how important it is to know exactly what you're doing with Vulkan. Now it's time to look at Vulkan devices in the system.

C++ code

 

 

 

 

https://vulkan-tutorial.tistory.com/11?category=649343 

 

Setup - Validation layers

Validation layers 이전 글 : Drawing Triangle - Creating an instance 다음 글 : Physical devices and queue families What are validation layers? 유효성 검사 계층이란 무엇입니까? Vulkan API는 드라이버..

vulkan-tutorial.tistory.com

https://vulkan-tutorial.com/en/Drawing_a_triangle/Setup/Validation_layers