編輯:關於Android編程
從前面的架構可以看到,從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這些vkXXX函數的定義在api_gen.cpp中(注意這些_gen.*形式的文件都是根據模板用apic工具生成的)。這裡看下大體流程,首先是vkCreateInstance()函數:(dlsym(libvulkan, "vkCreateInstance")); vkDestroyInstance = reinterpret_cast (dlsym(libvulkan, "vkDestroyInstance")); ...
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中的例子。
現在我目前知道可以獲取SharedPreferences實例的常用方法有三個: 1.通過Context的getSharedPreferences(String name,
Universal-Image-Loader是一個強大而又靈活的用於加載、緩存、顯示圖片的Android庫。它提供了大量的配置選項,使用起來非常方便。基本概念基本使用首次
一:ViewPager的含義:ViewPager的功能就是可以使視圖滑動,就像Lanucher左右滑動那樣。ViewPager用於實現多頁面的切換效果,該類存在於Goog
Gallery 的用法很簡單,百度一下一大堆。現在就結合1個效果的例子。 這個效果其實很簡單,設置未被選中的項,的透明度 周圍的看起來就會有點模糊vcD4KPH