原文地址:http://blog.csdn.net/jinzhuojun/article/details/52430543
从前面的架构可以看到,从app到GPU IHV提供的ICD之间,需要有vulkan runtime。在Android中,这个runtime主要位于/frameworks/native/vulkan目录,它会编译成libvulkan.so。主要作用是对driver的封装,及提供API hook能力,还有与本地native window的整合,同时它提供了一个ICD的参考实现null_driver。其中主要的几个目录包括api(用于生成api的模板),libvulkan(loader),nulldrv(默认ICD实现)。include下为vulkan暴露的头文件,其中最主要的两个头文件为vulkan.h(通用内容),vk_platform.h(平台相关内容)。
VK_DEFINE_HANDLE(VkInstance)
VK_DEFINE_HANDLE(VkPhysicalDevice)
VK_DEFINE_HANDLE(VkDevice)
....
而这些指针指向相应的driver中的结构VkXXX_T。这些结构首个成员都是hwvulkan_dispatch_t,其中的vtbl经初始化由loader设置指向相应的结构。
在/frameworks/native/vulkan目录下,有两个driver的实现:null_driver和stubhal。它们有些类似,都是真正driver不存在情况下的fallback。两者的区别在于,前者是硬件driver不存在下的fallback,类似于gralloc.defaut.so和hwcomposer.default.so,而后者的目的是loader在没有HAL实现的情况下避免每次检查HAL为null。
在接下去之前,先看一下vulkan中的一些基本相关背景。Vulkan中不再有全局状态,所有app相关的状态存在VkInstance中,所以app会先通过vkCreateInstance()创建instance,然后通过vkEnumeratePhysicalDevices()查询系统中的物理设备,接着用vkCreateDevice()根据指定物理设备创建逻辑设备。另外,根据vulkan spec,vulkan不必要静态暴露接口,接口函数的指针可以通过vkGetInstanceProcAddr()来获得,类似于OpenGL中的GetProcAddress系函数。而vkGetInstanceProcAddr函数本身是通过平台相关的loader来提供的。vkGetDeviceProcAddr()和其它接受VkInstance或VkPhysicalDevice为第一参数的函数地址可通过vkGetInstanceProcAddr()获得,它们是per-instance的;以VkDevice, VkQueue或VkCommandBuffer为第一参数的函数地址可通过vkGetDeviceProcAddr()获得,它们是per-device的。如果通过直接调用这些查询到的API地址就可以避免dispatch所带来的开销。
以https://github.com/googlesamples/android-vulkan-tutorials中的sample为例,一个app要使用vulkan,需要打开libvulkan.so,然后通过dlsym取其中的vulkan接口函数地址。这些common的code在vulkan_wrapper.cpp中:
void* libvulkan = dlopen("libvulkan.so", RTLD_NOW | RTLD_LOCAL);
if (!libvulkan)
return 0;
// Vulkan supported, set function addresses
vkCreateInstance = reinterpret_cast(dlsym(libvulkan, "vkCreateInstance"));
vkDestroyInstance = reinterpret_cast(dlsym(libvulkan, "vkDestroyInstance"));
...
这些vkXXX函数的定义在api_gen.cpp中(注意这些_gen.*形式的文件都是根据模板用apic工具生成的)。这里看下大体流程,首先是vkCreateInstance()函数:
VKAPI_ATTR VkResult vkCreateInstance(const VkInstanceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkInstance* pInstance) {
return vulkan::api::CreateInstance(pCreateInfo, pAllocator, pInstance);
}
它的真正实现在api.cpp中。为了简单,这里假设用的是null_driver,且没有validation layer。
CreateInstance()
EnsureInitialized()
// 初始化真正的driver,std::call_once()保证只调用一次。
driver::OpenHAL()
Hal::Open()
hw_get_module("vulkan", ...) // 初始化hwvulkan_module_t
module->common.methods->open(&module->common, HWVULKAN_DEVICE_0, ...) // 初始化hwvulkan_device_t
DiscoverLayers() // 查找layer lib,会在搜索路径下寻找validation layer的实现库。
LayerChain::CreateInstance()
LayerChain chain(...)
chain.ActivateLayers() // 从create info中取出layer信息并通过LoadLayer()加载,并将它们在SetupLayerLinks()里链接起来。
chain.Create(create_info, ...)
vulkan::driver::GetInstanceProcAddr(VK_NULL_HANDLE, "vkCreateinstance")
GetProcHook() // 先找是否有对应hook函数。这个hook列表在全局变量g_proc_hooks里。
// 对于vkCreateInstance,它在hook列表中,因此会返回该hook函数地址。
CreateInstance() // 位于driver.cpp中
AllocateInstanceData() // 分配每个instance所关联的InstanceData。
Hal::Device().CreateInstance() // ICD中的CreateInstance(),这里假设是null_driver.cpp里的CreateIntance()。
SetData() // 将InstanceData和VkInstance绑定。
InitDriverTable() // 实现在driver_gen.cpp中,将InstanceDriverTable结构InstanceData.driver中的API函数指针初始化好。
// 使用的是Hal::Device().GetInstanceProcAddr(),这里假设是null_driver.cpp中的GetInstanceProcAddr(),
// 它的实现在null_driver_gen.cpp中。
InitDispatchTable() // 实现在api_gen.cpp中。和上面类似,初始化InstanceDispatchTable结构InstanceData.dispatch。
// 这里用的是driver.cpp中的driver::GetInstanceProcAddr()。
// 像CreateAndroidSurfaceKHR这些平台相关的接口都是作为其中的扩展。
这里看下上面用到的driver::GetInstanceProcAddr()的实现,先是看该函数是否在hook表中,否则调用ICD的GetInstanceProcAddr()来搜索函数。这时返回的就是ICD中的相应实现了。
PFN_vkVoidFunction GetInstanceProcAddr(VkInstance instance, const char* pName) {
const ProcHook* hook = GetProcHook(pName);
if (!hook)
return Hal::Device().GetInstanceProcAddr(instance, pName);
vkCreateDevice()
CreateDevice() // 位于api.cpp
LayerChain::CreateDevice()
LayerChain chain()
chain.ActivateLayers() // Layer处理
chain.Create(physical_dev, ...)
driver::CreateDevice()
// 分配DeviceData
null_driver::CreateDevice()
SetData() // 将DeviceData绑定VkDevice.
InitDriverTable() // 这里使用的是null_driver::GetDeviceProcAddr()。
InitDispatchTable(dev, ) // 这里使用的是driver::GetDeviceProcAddr()。
初始化完后,app就可以调用vulkan的接口了。举例来说,当app调用了vkAllocateMemory(),该函数首先会调用到api_gen.cpp中的wrapper:
VKAPI_ATTR VkResult vkAllocateMemory(VkDevice device, const VkMemoryAllocateInfo* pAllocateInfo, const VkAllocationCallbacks* pAllocator, VkDeviceMemory* pMemory) {
return vulkan::api::AllocateMemory(device, pAllocateInfo, pAllocator, pMemory);
}
这里会从DeviceData中的跳板函数表dispatch中找AllocateMemory相应的地址并调用:
VKAPI_ATTR VkResult AllocateMemory(VkDevice device, const VkMemoryAllocateInfo* pAllocateInfo, const VkAllocationCallbacks* pAllocator, VkDeviceMemory* pMemory) {
return GetData(device).dispatch.AllocateMemory(device, pAllocateInfo, pAllocator, pMemory);
}
那这个函数地址指向哪里呢?从前面InitDispatchTable()可以看到,该地址是通过driver::GetInstanceProcAddr()取得的。而该函数中首先从g_proc_hooks全局表中找是否有相应的函数,失败的话则调用ICD中的GetDeviceProcAddr()来查找。
PFN_vkVoidFunction GetDeviceProcAddr(VkDevice device, const char* pName) {
const ProcHook* hook = GetProcHook(pName);
if (!hook)
return GetData(device).driver.GetDeviceProcAddr(device, pName);
这里是通过null_driver::GetDeviceProcAddr()查找就会返回null_driver::AllocateMemory。如果在平台上厂商有提供vulkan实现的话此时就会调用到厂商的相应实现中去。
Android中libvulkan做的事之一就是将这两者结合起来。首先ICD中需要实现几个Android相关的extension:vkGetSwapchainGrallocUsageANDROID,vkAcquireImageANDROID,vkQueueSignalReleaseImageANDROID。他们的用途一会儿会提到。对于app而言,典型的和WSI相关流程如下:
1. 先通过vkCreateAndroidSurfaceKHR() 创建VkSurfaceKHR。其实现位于swapchain.cpp中的CreateAndroidSurfaceKHR()。可以看到其中创建了driver::Surface对象并初始化。参数中传入的ANativeWindow会赋到创建的Surface对象window成员中。同时还可以看到,在Android平台上VkSurfaceKHR其实就是这个driver::Surface类的指针。这个Surface的作用是维护了ANativeWindow和VkSwapchain的对应关系。它与后面要创建的Swapchain相关的数据结构关系如下:
2. 通过vkCreateSwapchainKHR()创建VkSwapchainKHR。它的实现在swapchain.cpp中的CreateSwapchainKHR()。可以看到,其中对于driver::Surface中指向的libgui::Surface初始化了一坨属性(通过ANativeWindow接口)。其中会通过扩展接口vkGetSwapchainGrallocUsageANDROID()将vulkan中的属性转成gralloc能认的属性。另外会通过libgui::Surface从BufferQueue中取出buffer(ANativeWindowBuffer)并转为VkImage。这个过程依赖于对vkCreateImage()的扩展。转化过程中首先根据ANativeWindowBuffer中的值构造VkNativeBufferAndroid,然后VkNativeBufferAndroid作为vkCreateImage()的参数创建相应的VkImage。ANativeWindowBuffer和VkImage的对应关系由driver::Swapchain::Image数组来维护(如上图)。数组中元素的最大个数与libgui中BufferQueue中的定义一样。另外VkSwapchainKHR其实就是Swapchain的指针。
前两步相当于EGL中的eglCreateWindowSurface(),完成surface的初始化,大体流程图如下:
3. 每当需要绘制新的一帧时,先调用vkAcquireNextImageKHR()获得一个app可用的buffer。该buffer由上面提到的Swapchain中的images数组的index表示。但此时可能GPU还是在操作该buffer,因此拿到后还需等返回的semaphore signal后才能确认该buffer真正可用。接下来就可以真正渲染了。
4. 渲染完一帧后,调用vkQueuePresentKHR()提交前面获取的buffer。同样的,buffer用index表示。
后两步大体流程图如下:
vkAcquireNextImageKHR()和vkQueuePresentKHR()的实现分别位于swapchain.cpp中的AcquireNextImageKHR()和QueuePresentKHR()。前者本质是调用libgui::Surface的dequeueBuffer()拿到一个buffer,然后找到该buffer在driver::Swapchain::Image数组中的index并返回。后者本质上是调用queueBuffer()将该buffer放回到BufferQueue中去。可以看到,本质上这些WSI相关接口就是把vulkan中的接口转为Android中的相应接口。相应的数据结构也需做类似的转化。
还有个问题就是Android中的buffer同步使用的是fence fd(通过ANDROID_native_fence_sync扩展),vulkan中使用的是VkSemaphore和VkFence,因此这之间也需要转换。这时上面提到的vkAcquireImageANDROID()和vkQueueSignalReleaseImageANDROID()就起到作用了。前者将native fence fd转为VkSemaphore和VkFence;后者创建一个native fence fd。
另外VK_EXT_debug_report扩展可以允许app在指定的一些点调用自定义的回调函数。详见https://developer.android.com/ndk/guides/graphics/validation-layer.html#debug及https://github.com/googlesamples/android-vulkan-tutorials中的例子。