Erel
Erel

Reputation: 739

Overriding VK_USE_64_BIT_PTR_DEFINES

Vulkan handles are either opaque pointers to structs, or simply unsigned 64-bit integers depending on the value of VK_USE_64_BIT_PTR_DEFINES:

    #if (VK_USE_64_BIT_PTR_DEFINES==1)
        #define VK_DEFINE_NON_DISPATCHABLE_HANDLE(object) typedef struct object##_T *object;
    #else
        #define VK_DEFINE_NON_DISPATCHABLE_HANDLE(object) typedef uint64_t object;
    #endif

VK_USE_64_BIT_PTR_DEFINES is set, when not already defined, in vulkan_core.h to the following values depending on the platform (at least on my version of the header):

#ifndef VK_USE_64_BIT_PTR_DEFINES
    #if defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__) ) || defined(_M_X64) || defined(__ia64) || defined (_M_IA64) || defined(__aarch64__) || defined(__powerpc64__)
        #define VK_USE_64_BIT_PTR_DEFINES 1
    #else
        #define VK_USE_64_BIT_PTR_DEFINES 0
    #endif
#endif

The documentation for that preprocessor #define states the following:

The vulkan_core.h header allows the VK_USE_64_BIT_PTR_DEFINES definition to be overridden by the application. This allows the application to select either a 64-bit pointer type or a 64-bit unsigned integer type for non-dispatchable handles in the case where the predefined preprocessor check does not identify the desired configuration.

I interpret this declaration as:

It's okay to force VK_USE_64_BIT_PTR_DEFINES's value at build time so that I can use either of the definitions.

But, to the best of my knowledge, changing the definitions on header-side only has... Implications for shared libraries compiled as .dll / .so, obviously including the ones of the LunarG's Vulkan SDK I am using (we're not only changing types, we are also changing function signatures).

On Linux or on Windows with MinGW, VK_USE_64_BIT_PTR_DEFINES is 1 by default. On Windows using MSVC, it defaults to 0. So I tried changing it to 1, which, as expected, generates the following linker error (MRE used for that test below).

main.cpp.obj : error LNK2019: unresolved external symbol _vkCmdBindIndexBuffer@20 referenced in function "void __cdecl foo(void)" (?foo@@YAXXZ) vk_test.exe : fatal error LNK1120: 1 unresolved externals

Edit - clarification after comment: everything is extern "C"{} in vulkan_core.h.


I highly, highly doubt the Vulkan documentation is wrong, so I assume I must be missing something very obvious. I suspect the answer is either:

But I don't really know how to confirm my conjunctures.


MRE:

CMakeLists.txt

cmake_minimum_required(VERSION 3.23)

project(vk_test LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

find_package(Vulkan REQUIRED)

add_executable(vk_test main.cpp)

# No effect for GCC / MinGW
# On MSVC on the other hand, the value is set to 0 when not defined by user
# Comment that line to build with MSVC
add_compile_definitions(VK_USE_64_BIT_PTR_DEFINES=1)

target_link_libraries(vk_test PUBLIC ${Vulkan_LIBRARY})
target_include_directories(vk_test SYSTEM PUBLIC ${Vulkan_INCLUDE_DIRS})

main.cpp:

#include <cassert>

#include <vulkan/vulkan_core.h>

void foo()
{
    assert(false); // The following call has invalid parameters, so no point letting execution reach that point
    vkCmdBindIndexBuffer(VK_NULL_HANDLE, VK_NULL_HANDLE, 0, VkIndexType{});
}

int main()
{
    return 0;
}

System:

Upvotes: 4

Views: 240

Answers (1)

Erel
Erel

Reputation: 739

Okay, just a little bit more research allowed me to clear things up.

TL;DR: it's not meant to be used like that (so I was partially right in my conjunctures, yay!). Overriding VK_USE_64_BIT_PTR_DEFINES makes it so you cannot use the "raw" definitions of the lib' you're linking with for, well, the reasons I was worried about in my post. Instead, you first create a VkInstance, and then get the function pointers using vkGetInstanceProcAddr (resp. with a VkDevice and vkGetDeviceProcAddr for device functions). All the functions required to get the relevant function pointers are accessible from the lib'.


A simple demo of what you are expected to do when overriding VK_USE_64_BIT_PTR_DEFINES

cmake_minimum_required(VERSION 3.23)

project(vk_test LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

find_package(Vulkan REQUIRED)

add_executable(vk_test main.cpp)

# No effect for GCC / MinGW
# On MSVC on the other hand, the value is set to 0 when not defined by user
add_compile_definitions(VK_USE_64_BIT_PTR_DEFINES=1)

target_link_libraries(vk_test PUBLIC ${Vulkan_LIBRARY})
target_include_directories(vk_test SYSTEM PUBLIC ${Vulkan_INCLUDE_DIRS})
#include <iostream>

#include <vulkan/vulkan_core.h>

void foo()
{
    const VkInstanceCreateInfo instance_info
    {
        /*.sType                   = */VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
        /*.pNext                   = */nullptr,
        /*.flags                   = */0,
        /*.pApplicationInfo        = */nullptr,
        /*.enabledLayerCount       = */0,
        /*.ppEnabledLayerNames     = */nullptr,
        /*.enabledExtensionCount   = */0,
        /*.ppEnabledExtensionNames = */nullptr
    };

    VkInstance instance = VK_NULL_HANDLE;

    if(vkCreateInstance(&instance_info, nullptr, &instance) != VK_SUCCESS)
    {
        std::cout << ":(" << std::endl;
        return;
    }

    const auto f = reinterpret_cast<PFN_vkCmdBindIndexBuffer>(vkGetInstanceProcAddr(instance, "vkCmdBindIndexBuffer"));
    std::cout << (f != nullptr) << std::endl; // Prints '1' (hopefully for you too)

    vkDestroyInstance(instance, nullptr);
}

int main()
{
    foo();

    return 0;
}

Alternatives / tools to avoid having to write such code yourself

  • There is a generated list of all the required calls in vulkan.hpp, which hopefully can be integrated in your application with less efforts than writing this by hand.

  • Vulkan-Loader (VOLK) is a Khronos-developed library for that purpose. It also has a list of the symbols that can be loaded in such a way stored as plain text, which can also be used to generate appropriate code.

Upvotes: 3

Related Questions