
Vulkan loader가 필요한 이유는 vulkan이 cross-platform이기 때문이다. 따라서 android, Linux, Windows, ... 등에 모두 사용할 수 있게끔 추상화를 한 것과 다름이 없다.
Vulkan Loader는 Application, Layer, Vulkan driver 사이의 중개자 역할을 한다. 여기서 Vulkan driver는 Vulkan loader에게 명령받은 것을 실제 GPU 명령어를 실행시킨다. 즉, vulkan driver의 관리 업체가 '제조사'이다.
흔히 vulkan을 Nvidia에서 실행시키면 내부적으로는 CUDA로 동작하고, 애플 제품을 쓰면 Metal로 내부적으로 동작한다는 말이 여기서 나온다.
Vulkan Loader가 운영체제 단에서 동작하다가 Vulkan의 API 함수를 호출하면 GPU에게 해당 함수의 내용을 정의된 Instance Layer 순으로 동작하며 GPU에게 명령을 한다고 보면 된다.
이렇게 정의된 Instance Layer 순으로 동작하다가 GPU에게 전달하는 형태로 설계한 이유는 실제 Release할 때는 Instance Layer를 제거하기만 하면 디버깅 역할을 쉽게 없에서 성능을 높이는 것이다.
Vulkan Loader는 추가적으로 생성해야 되는 것이 아니라 (Vulkan Loader는 Vulkan library로 공유 라이브러리인 libvulkan.so, vulkan.dll이다.), 운영체제에서 동작하기에 Vulkan instance를 인터페이스로 삼아서 명령을 전달하면 된다.

(instance) Layer는 vulkan loader에 의해 로드되고, 프로그램에 삽입되는 공유 라이브러리 패키지이다. 사용할 레어어는 프로그램에 의해 명시적으로 활성화되거나 vulkan loader에 이를 사용하도록 지시하면 된다.

vulkan loader에 이를 지시하는 방법 중 하나는 Vulkan Configuration을 통해 설정을 해주면 된다.

Vulkan loader는 레지스트리를 검색해서 레이어 목록을 찾아낸다.
각 layer에 대한 정보는 해당 파일을 보면 된다.

Implicit Layer은 Vulkan에서 자동으로 필요 시에 호출하고,
Explicit Layer는 instance에서 명시적으로 호출해줘야 한다.
파일의 instance_extensions를 보면 해당 layer가 필요로 하는 extension이 나온다.

Vulkan은 GPU가 실제로 작동하는 방식을 직접 제어할 수 있는 명시적 API이다.
설계상 고성능을 위해 Vulkan 드라이버 내에서는 최소한의 오류 검사가 수행되기에 프로그램이 잘 만들어진 것을 가정한다.
Khronos Valiation Layer은 개발자가 자신의 프로그램이 Vulkan API를 올바르게 사용하는지 확인함으로써 개발을 지원하기 위해 사용된다.
validation instance layer : "VK_LAYER_KHRONOS_validation"
validation instance extension : "VK_EXT_debug_utils"
VK_EXT_debug_utils 는 프로그램에서 제공되는 콜백에 유효성 검사 계층 메시지를 전달하는 디버그 메신저를 만들 수 있다.
instance extension을 통해서 추가해야 하며, 프로그램이 validation layer에 여러 메신저를 등록할 수 있다. 각 메신저는 로그 메시지가 발생할 때 메시지 콜백을 실행시킨다.
각 메시지는 message severity(메시지 심각도)와 message type으로 분류된다. message severity는 오류, 경고, 정보로 분류되며, message type은 유효성 검사, 성능 등을 포함한다.

이 extension과 비슷한 기능을 제공하는 지원 중단된 extension으로는 VK_EXT_debug_report가 있다. 이는 옛 vulkan driver를 사용하는 device에 적합하다.
마찬가지로 VK_EXT_validation_flags 라는 extension 또한 지원 중단되었다.
VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT
VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT
VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT
VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT
먼저 message callback을 만들면 된다.
static VKAPI_ATTR VkBool32 VKAPI_CALL exampleDebugCallback(
VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
VkDebugUtilsMessageTypeFlagsEXT messageType,
const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
void* pUserData)
{
std::string str("validation layer : ");
switch (messageSeverity) {
case VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT :
str = "[VERBOSE] ";
break;
case VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT :
str = "[INFO] ";
break;
case VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT :
str = "[WARNING] ";
break;
case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT :
str = "[ERROR] ";
break;
}
std::cerr << str << pCallbackData->pMessage << std::endl;
return VK_FALSE;
}
Vulkan이 함수를 호출할 수 있도록 VKAPI_ATTR, VKAPI_CALL를 붙여줘야 한다.
다음은 메신저를 만드는 코드이다.
void createDebugUtilsMessengerEXT(
VkInstance instance,
const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo,
VkAllocationCallbacks* pAllocator,
VkDebugUtilsMessengerEXT* pDebugMessenger)
{
auto func = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT");
if (func != nullptr)
func(instance, pCreateInfo, pAllocator, pDebugMessenger);
else
VK_CALL(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);
}
Extension은 공유 라이브러리를 instance에 추가하는 것이라고 했다. 즉, 공유 라이브러리의 함수를 호출하기 위해서 instance로부터 vkCreateDebugUtilsMessengerEXT, vkDestroyDebugUtilsMessengerEXT 함수를 가져와서 실행시키게끔 wrapping 함수를 만들어준다.
void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo, PFN_vkDebugUtilsMessengerCallbackEXT callback) {
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 | VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_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 | VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT;
createInfo.pfnUserCallback = callback;
}
void setupDebugMessenger(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, PFN_vkDebugUtilsMessengerCallbackEXT callback) {
VkDebugUtilsMessengerCreateInfoEXT createInfo{};
populateDebugMessengerCreateInfo(createInfo, exampleDebugCallback);
createDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger);
}
다만 wrapping 함수를 실행시키기 위한 인자들이 필요하고, 이를 채워서 실행시킨다.
위에서 공유 라이브러리를 instance에서 갖고온다고 했다.
따라서 instance에도 VkDebugUtilsMessengerCreateInfoEXT를 추가해줘야 Messenger를 만들 수 있다.
const std::vector<const char*> validationLayers = { "VK_LAYER_KHRONOS_validation" };
VkDebugUtilsMessengerCreateInfoEXT createInfo{};
VulkanHelper::populateDebugMessengerCreateInfo(createInfo, exampleDebugCallback);
uint32_t glfwExtensionCount = 0;
const char** glfwExtensions;
glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
std::vector<const char*> extensions(glfwExtensions, glfwExtensions + glfwExtensionCount);
extensions.push_back("VK_EXT_debug_utils");
VkInstance instance;
VkApplicationInfo appInfo{};
appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
appInfo.pApplicationName = "vulkan engine";
appInfo.pEngineName = "vulkan engine";
appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
appInfo.apiVersion = VK_API_VERSION_1_0;
VkInstanceCreateInfo instanceInfo{};
instanceInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
instanceInfo.pApplicationInfo = &appInfo;
instanceInfo.enabledExtensionCount = extensions.size();
instanceInfo.enabledLayerCount = validationLayers.size();
instanceInfo.ppEnabledExtensionNames = extensions.data();
instanceInfo.ppEnabledLayerNames = validationLayers.data();
instanceInfo.pNext = &createInfo;
VK_CALL(vkCreateInstance(&instanceInfo, nullptr, &instance));
VkDebugUtilsMessengerEXT debugMessenger;
setupDebugMessenger(instance, debugMessenger, exampleDebugCallback)
정리하면 다음과 같다.
1. Validation에 대한 callback 함수를 정의한다.
2. Validation Layer("VK_LAYER_KHRONOS_validation")를 instance를 생성할 때 넣는다.
3. instance extension("VK_EXT_debug_utils")를 instance 생성할 때 넣는다.
4. instance를 생성할 때 VKInstanceCreateInfo 구조체의 pNext 변수에 extension("VK_EXT_debug_utils")에 대한 Messenger 생성 정보 구조체를 넣는다.
5. instance를 생성한다.
6. instance로부터 (extension 공유 라이브러리에서) Messenger 생성 함수의 포인터를 얻어 실행한다.
7. instance가 삭제 될 때 instance로부터 (extension 공유 라이브러리에서) Messenger 삭제 함수의 포인터를 얻어 실행한다.
참고 코드 : https://github.dev/Overv/VulkanTutorial/blob/main/code/05_window_surface.cpp
https://vulkan.lunarg.com/doc/view/latest/windows/validation_layers.html
https://lifeisforu.tistory.com/399
https://www.khronos.org/assets/uploads/developers/library/2017-vulkan-loader-webinar/VulkanLoaderDeepDive_Khronos_Mar17.pdf
https://developer.android.com/ndk/guides/graphics/validation-layer?hl=ko
https://vulkan-tutorial.com/Drawing_a_triangle/Setup/Validation_layers