c++callbackvulkanvalidation-layers

How does the Vulkan Validation Layer assignment work?


I have started with Vulkan and just did the Validation Layers. In here, they add a static function static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(...) to the .cpp file, then simply assign

createInfo.pfnUserCallback = debugCallback;

How (/why) does this work? I know of callbacks of course, but I tried to add this function to my renderer class instead of just "locally"(?) in the .cpp file, first in the header file:

static VKAPI_ATTR VkBool32 VKAPI_CALL DebugCallback(
VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
VkDebugUtilsMessageTypeFlagsEXT messageType,
const VkDebugUtilsMessengerCallbackDataEXT* callbackData,
void* pUserData);

And then in the .cpp file:

VKAPI_ATTR VkBool32 VKAPI_CALL VulkanRenderer::DebugCallback(
VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
VkDebugUtilsMessageTypeFlagsEXT messageType,
const VkDebugUtilsMessengerCallbackDataEXT* callbackData,
void* pUserData)
{
    //...
}

And finally using assigning it like this:

createInfo.pfnUserCallback = &VulkanRenderer::DebugCallback;

My version does in fact not work, so I probably am thinking about the wrong thing here. But I don't know what they did in the tutorial or even what to search for to understand this.

I do know what code to write to make it work like they have it, I just don't know why.


Solution

  • When DebugCallback is a non-static member function, then &VulkanRenderer::DebugCallback is a member function pointer.

    A member function pointer is not a function pointer, but pfnUserCallback wants a function pointer.

    Member function pointers and function pointers can't work the same because a non-static member function needs to know on which object it is being called, which isn't the case for a free function.

    Now, Vulkan is a C API, not a C++ API, so it isn't aware of member functions at all and also uses a C idiom to implement the callback interface instead of a C++ one.

    The C idiom however still allows passing information about the object on which you want to call your member function. That's what pUserData is for. It will be passed through unchanged to your callback and the callback can use it to e.g. do a member function call:

    createInfo.pUserData = this;
    createInfo.pfnUserCallback = [](auto a, auto b, auto c, void* pUserData){ return static_cast<VulkanRenderer*>(pUserData)->DebugCallback(a, b, c); };
    

    (capture-less generic lamdbas can be converted into function pointers for specific argument types.)

    (I don't actually know much about Vulkan. If VKAPI_CALL and VKAPI_ATTR are required on the function that will be called by Vulkan, then a lambda might not work. In that case use a static member function with a name on which you can apply the macros instead of an nameless lambda on which you likely can't.)


    Alternatively, if DebugCallback doesn't need to be non-static, you can of course also declare it as static member function. Then your approach will also work. A static member function doesn't need any additional information about an object on which it is called and therefore forms normal function pointers like free functions.