基础知识

关于dll

介绍

Vulkan API 是硬件厂商提供的,在驱动里.安装Vulkan SDK后,开发者不用关心驱动层,可以通过(windows)vulkan-1.dll或(linux)libvulkan.so.1访问Vulkan API.

loadlibrary的方式

1
2
3
4
5
6
7
8
9
10
11
#if defined _WIN32
vulkan_library = LoadLibrary( "vulkan-1.dll" );
#elif defined __linux
vulkan_library = dlopen( "libvulkan.so.1", RTLD_NOW );
#endif
if( vulkan_library == nullptr ) {
std::cout << "Could not connect with a Vulkan Runtime library." <<
std::endl;
return false;
}
return true;

加载Vulkan API函数

方式

第一种方式:可以进行静态链接,并使用vulkan.h里定义的函数原型

第二种方式:不用vulkan.h了定义的原型,而是动态加载函数指针

第一种方式简单,但是每次调用函数时都会重定向一次,效率更低

第二种方式无重定向过程,且可以选择子集,但麻烦点,基本都用这种方式的,这里只介绍这种方式

方式

1.定义VK_NO_PROTOTYPES或者在引入vulkan.h前定义#define VK_NO_PROTOTYPES

2.创建新文件名为ListOfVulkanFunctions.inl

输入一下内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#ifndef EXPORTED_VULKAN_FUNCTION
#define EXPORTED_VULKAN_FUNCTION( function )
#endif
#undef EXPORTED_VULKAN_FUNCTION
//
#ifndef GLOBAL_LEVEL_VULKAN_FUNCTION
#define GLOBAL_LEVEL_VULKAN_FUNCTION( function )
#endif
#undef GLOBAL_LEVEL_VULKAN_FUNCTION
//
#ifndef INSTANCE_LEVEL_VULKAN_FUNCTION
#define INSTANCE_LEVEL_VULKAN_FUNCTION( function )
#endif
#undef INSTANCE_LEVEL_VULKAN_FUNCTION
//
#ifndef INSTANCE_LEVEL_VULKAN_FUNCTION_FROM_EXTENSION
#define INSTANCE_LEVEL_VULKAN_FUNCTION_FROM_EXTENSION( function,
extension )
#endif
#undef INSTANCE_LEVEL_VULKAN_FUNCTION_FROM_EXTENSION
//
#ifndef DEVICE_LEVEL_VULKAN_FUNCTION
#define DEVICE_LEVEL_VULKAN_FUNCTION( function )
#endif
#undef DEVICE_LEVEL_VULKAN_FUNCTION
//
#ifndef DEVICE_LEVEL_VULKAN_FUNCTION_FROM_EXTENSION
#define DEVICE_LEVEL_VULKAN_FUNCTION_FROM_EXTENSION( function,
extension )
#endif
#undef DEVICE_LEVEL_VULKAN_FUNCTION_FROM_EXTENSION

创建头文件VulkanFunctions.h,输入一下内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include "vulkan.h"
namespace vkapi {
#define EXPORTED_VULKAN_FUNCTION( name ) extern PFN_##name name;
#define GLOBAL_LEVEL_VULKAN_FUNCTION( name ) extern PFN_##name
name;
#define INSTANCE_LEVEL_VULKAN_FUNCTION( name ) extern PFN_##name
name;
#define INSTANCE_LEVEL_VULKAN_FUNCTION_FROM_EXTENSION( name,
extension ) extern PFN_##name name;
#define DEVICE_LEVEL_VULKAN_FUNCTION( name ) extern PFN_##name
name;
#define DEVICE_LEVEL_VULKAN_FUNCTION_FROM_EXTENSION( name,
extension ) extern PFN_##name name;
#include "ListOfVulkanFunctions.inl"
} // namespace vkapi

创建源文件VulkanFunctions.cpp,输入一下内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include "VulkanFunctions.h"
namespace vkapi {
#define EXPORTED_VULKAN_FUNCTION( name ) PFN_##name name;
#define GLOBAL_LEVEL_VULKAN_FUNCTION( name ) PFN_##name name;
#define INSTANCE_LEVEL_VULKAN_FUNCTION( name ) PFN_##name name;
#define INSTANCE_LEVEL_VULKAN_FUNCTION_FROM_EXTENSION( name,
extension ) PFN_##name name;
#define DEVICE_LEVEL_VULKAN_FUNCTION( name ) PFN_##name name;
#define DEVICE_LEVEL_VULKAN_FUNCTION_FROM_EXTENSION( name,
extension ) PFN_##name name;
#if defined _WIN32
#define LoadFunction GetProcAddress
#elif defined __linux
#define LoadFunction dlsym
#endif
#define EXPORTED_VULKAN_FUNCTION( name ) \
name = (PFN_##name)LoadFunction( vulkan_library, #name ); \
if( name == nullptr ) { \
std::cout << "Could not load exported Vulkan function named: " \
#name << std::endl; \
return false; \
}
#include "ListOfVulkanFunctions.inl"
} // namespace vkapi

通过在ListOfVulkanFunctions.inl中添加新的函数名可以加载更多vulkan api

函数名原型:name的原型为PFN_name

分为3类

global-level,instance-level,device-level

global-level

创建变量

PFN_vkEnumerateInstanceExtensionProperties

PFN_vkEnumerateInstanceLayerProperties

PFN_vkCreateInstance

并调用vkGetInstanceProcAddr获取到对于函数指针

在ListOfVulkanFunctions.inl中添加

1
2
3
4
5
6
7
#ifndef GLOBAL_LEVEL_VULKAN_FUNCTION
#define GLOBAL_LEVEL_VULKAN_FUNCTION( function )
#endif
GLOBAL_LEVEL_VULKAN_FUNCTION( vkEnumerateInstanceExtensionProperties )
GLOBAL_LEVEL_VULKAN_FUNCTION( vkEnumerateInstanceLayerProperties )
GLOBAL_LEVEL_VULKAN_FUNCTION( vkCreateInstance )
#undef GLOBAL_LEVEL_VULKAN_FUNCTION

在VulkanFunctions.cpp的#include “ListOfVulkanFunctions.inl”前添加

1
2
3
4
5
6
7
#define GLOBAL_LEVEL_VULKAN_FUNCTION( name ) \
name = (PFN_##name)vkGetInstanceProcAddr( nullptr, #name ); \
if( name == nullptr ) { \
std::cout << "Could not load global-level function named: " \
#name << std::endl; \
return false; \
}

instance-level

变量声明一样的

1
2
3
4
5
6
7
8
vkEnumeratePhysicalDevices
vkGetPhysicalDeviceProperties
vkGetPhysicalDeviceFeatures
vkGetPhysicalDeviceQueueFamilyProperties
vkCreateDevice
vkGetDeviceProcAddr
vkDestroyInstance
vkEnumerateDeviceExtensionProperties

不同之处是,调用vkGetInstanceProcAddr获取函数指针,第一个参数为instance

如何判断一个函数是instance-level还是device-level?

所有device-level函数第一个参数为VkDevice,VkQueue,VkCommandBuffer,因此所有第一个参数不是这些且不是global-level函数的就是device-level

需要额外注意的是,extensions能引入新的函数,需要加载它们,以便在程序中调用.

为了加载instance-level函数,对ListOfVulkanFunctions.inl进行更新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#ifndef INSTANCE_LEVEL_VULKAN_FUNCTION
#define INSTANCE_LEVEL_VULKAN_FUNCTION( function )
#endif
INSTANCE_LEVEL_VULKAN_FUNCTION( vkEnumeratePhysicalDevices )
INSTANCE_LEVEL_VULKAN_FUNCTION( vkGetPhysicalDeviceProperties )
INSTANCE_LEVEL_VULKAN_FUNCTION( vkGetPhysicalDeviceFeatures )
INSTANCE_LEVEL_VULKAN_FUNCTION( vkCreateDevice )
INSTANCE_LEVEL_VULKAN_FUNCTION( vkGetDeviceProcAddr )
//...
#undef INSTANCE_LEVEL_VULKAN_FUNCTION
//
#ifndef INSTANCE_LEVEL_VULKAN_FUNCTION_FROM_EXTENSION
#define INSTANCE_LEVEL_VULKAN_FUNCTION_FROM_EXTENSION( function, extension
)
#endif
INSTANCE_LEVEL_VULKAN_FUNCTION_FROM_EXTENSION(
vkGetPhysicalDeviceSurfaceSupportKHR, VK_KHR_SURFACE_EXTENSION_NAME )
INSTANCE_LEVEL_VULKAN_FUNCTION_FROM_EXTENSION(
vkGetPhysicalDeviceSurfaceCapabilitiesKHR, VK_KHR_SURFACE_EXTENSION_NAME )
INSTANCE_LEVEL_VULKAN_FUNCTION_FROM_EXTENSION(
vkGetPhysicalDeviceSurfaceFormatsKHR, VK_KHR_SURFACE_EXTENSION_NAME )
#ifdef VK_USE_PLATFORM_WIN32_KHR
INSTANCE_LEVEL_VULKAN_FUNCTION_FROM_EXTENSION( vkCreateWin32SurfaceKHR,
VK_KHR_WIN32_SURFACE_EXTENSION_NAME )
#elif defined VK_USE_PLATFORM_XCB_KHR
INSTANCE_LEVEL_VULKAN_FUNCTION_FROM_EXTENSION( vkCreateXcbSurfaceKHR,
VK_KHR_XLIB_SURFACE_EXTENSION_NAME )
#elif defined VK_USE_PLATFORM_XLIB_KHR
INSTANCE_LEVEL_VULKAN_FUNCTION_FROM_EXTENSION( vkCreateXlibSurfaceKHR,
VK_KHR_XCB_SURFACE_EXTENSION_NAME )
#endif
#undef INSTANCE_LEVEL_VULKAN_FUNCTION_FROM_EXTENSION

更新VulkanFunctions.cpp

1
2
3
4
5
6
7
#define INSTANCE_LEVEL_VULKAN_FUNCTION( name ) \
name = (PFN_##name)vkGetInstanceProcAddr( instance, #name ); \
if( name == nullptr ) { \
std::cout << "Could not load instance-level Vulkan function named: "\
#name << std::endl; \
return false; \
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#define INSTANCE_LEVEL_VULKAN_FUNCTION_FROM_EXTENSION( name, extension )
\
for( auto & enabled_extension : enabled_extensions ) { \
if( std::string( enabled_extension ) == std::string( extension ) )
{ \
name = (PFN_##name)vkGetInstanceProcAddr( instance, #name ); \
if( name == nullptr ) { \
std::cout << "Could not load instance-level Vulkan function named: "
\
#name << std::endl; \
return false; \
} \
} \
}

device-level

创建logical device(VkDevice logical_device)后,需要渲染3D场景、计算对象碰撞,运行视频帧的时候

  • 与前述基本一样,不同之处在于

    调用vkGetDeviceProcAddr(device,”“)获得函数指针

机核所有3D渲染软件都是使用device-level 函数进行工作的,它们用来创建buffers,images,samplers,shaders.使用device-level函数创建pipeline objects,同步primitives,framebuffers和很多其他资源.并且,最重要的,记录之后提交给queues让硬件执行的操作.

device-level函数可以通过vkGetInstanceProcAddr获取指针,但它不是最理想的.Vulkan被设计为灵活的API,它可以在单个程序里的多个devices上执行操作,但当我们调用vkGetInstanceProcAddr函数,我们不能提供任何与logical device关联的参数,因此这个函数返回的指针不能与device关联.device可能在调用vkGetInstanceProcAddr时都不存在,这是该调用返回一个dispatch函数–根据其参数,调用适用于给定的logical device的函数的实现.但是,这个jump有性能开销:很小但任然会消耗运行时间.

如果像避免这种不必要的jump且请求已给device的函数指针,我们需要调用vkGetDeviceProcAddr,这样能避免中间调用,这种方式有些缺陷:需要给app里所有device都请求各自的函数的函数指针.如果有多个device,需要给每个logical device自己的函数指针列表.使用C++预处理可以很容易获得已知device的函数的函数指针.

如何判断函数是device-level?

第一个参数是VkDevice,VkQueue,VkCommandBuffer

为了加载device-level函数,需要更新ListOfVulkanFunctions.inl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#ifndef DEVICE_LEVEL_VULKAN_FUNCTION
#define DEVICE_LEVEL_VULKAN_FUNCTION( function )
#endif
DEVICE_LEVEL_VULKAN_FUNCTION( vkGetDeviceQueue )
DEVICE_LEVEL_VULKAN_FUNCTION( vkDeviceWaitIdle )
DEVICE_LEVEL_VULKAN_FUNCTION( vkDestroyDevice )
DEVICE_LEVEL_VULKAN_FUNCTION( vkCreateBuffer )
DEVICE_LEVEL_VULKAN_FUNCTION( vkGetBufferMemoryRequirements )
// ...
#undef DEVICE_LEVEL_VULKAN_FUNCTION
//
#ifndef DEVICE_LEVEL_VULKAN_FUNCTION_FROM_EXTENSION
#define DEVICE_LEVEL_VULKAN_FUNCTION_FROM_EXTENSION( function, extension )
#endif
DEVICE_LEVEL_VULKAN_FUNCTION_FROM_EXTENSION( vkCreateSwapchainKHR,
VK_KHR_SWAPCHAIN_EXTENSION_NAME )
DEVICE_LEVEL_VULKAN_FUNCTION_FROM_EXTENSION( vkGetSwapchainImagesKHR,
VK_KHR_SWAPCHAIN_EXTENSION_NAME )
DEVICE_LEVEL_VULKAN_FUNCTION_FROM_EXTENSION( vkAcquireNextImageKHR,
VK_KHR_SWAPCHAIN_EXTENSION_NAME )
DEVICE_LEVEL_VULKAN_FUNCTION_FROM_EXTENSION( vkQueuePresentKHR,
VK_KHR_SWAPCHAIN_EXTENSION_NAME )
DEVICE_LEVEL_VULKAN_FUNCTION_FROM_EXTENSION( vkDestroySwapchainKHR,
VK_KHR_SWAPCHAIN_EXTENSION_NAME )
#undef DEVICE_LEVEL_VULKAN_FUNCTION_FROM_EXTENSION

更新VulkanFunctions.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#define DEVICE_LEVEL_VULKAN_FUNCTION( name ) \
name = (PFN_##name)vkGetDeviceProcAddr( device, #name ); \
if( name == nullptr ) { \
std::cout << "Could not load device-level Vulkan function named: " #name
<< std::endl; \
return false; \
}
#define DEVICE_LEVEL_VULKAN_FUNCTION_FROM_EXTENSION( name,
extension ) \
for( auto & enabled_extension : enabled_extensions ) { \
if( std::string( enabled_extension ) == std::string( extension )
) { \
name = (PFN_##name)vkGetDeviceProcAddr( logical_device, #name );\
if( name == nullptr ) { \
std::cout << "Could not load device-level Vulkan function named: "
\
#name << std::endl; \
return false; \
} \
} \
}

释放Vulkan Loader library

1
2
3
4
5
6
#if defined _WIN32
FreeLibrary( vulkan_library );
#elif defined __linux
dlclose( vulkan_library );
#endif
vulkan_library = nullptr;