Vulkan在Android使用Compute shader
oeip 相關功能只能執行在window平臺,想移植到android平臺,暫時選擇vulkan做為影象處理,主要一是裡面有單獨的計算管線且支援好,二是熟悉下最新的渲染技術思路。
這個 demo(git地址) 的功能很簡單,在android下,利用vulkan的compute shader對輸入圖進行1-x的執行後,把計算結果複製到當前交換鏈里正在渲染的影象上顯示出來。
本文主要記錄其中一些過程,因為第一次嘗試類似開發,所以有誤的地方歡迎大家指出。
前期準備工作主要如下,VSCode C++環境配置,熟悉CMake。
為什麼選擇vscode,而不是visual studio/android studio,主要是基於如下考慮,首先在win平臺方便除錯與測試,在win平臺完成demo後再移植到android下就方便了,而visual studio/android studio分別在開發原生win/android下方便,而vscode+cmake的組合很方便在win平臺除錯測試,然後平穩生成相應的android studio專案方案,然後在android studio裡進行除錯封裝,並且最新android studio首選cmake構建,更方便整合。VSCode必需的C++外掛主要是如下幾個 C/C++ for Visual Studio Code/Cmake/Cmake tools.
然後我花了一些時間在vscode裡編譯了ogre-next,並把它的cmake檔案跟了一遍,大致瞭解了cmake的用法,總結了下 CMake常用命令 。
vulkan結合github上二個vulkan 的demo方案,初步瞭解vulkan API與渲染流程。vulkan網上說的二千多行程式碼畫個三角形確實一點也不誇張,Vulkan API粒度細,控制度高,以及為多執行緒渲染設計的渲染佇列,渲染命令及同步,所以程式碼量看著就上去了,但是你根據你的需求簡單封裝下,如交換鏈,渲染管線,UBO,buffer等,再寫也就大部分業務邏輯程式碼。簡單來說,先根據demo熟悉流程與API,再手動寫個2K多行的簡單demo,在這過程,通過比較以前opengl/dx的API流程熟悉與加深思路,然後根據你的需求確定一些引數,封裝一些類,最後開始你的需求並反向不斷完善更新你的封裝庫。
知乎上各位大佬已經把Vulkan API/Demo講解的非常清晰,這裡就說下這個DEMO的流程,供大家參考,歡迎大家指出理解有誤的部分。
首先視窗初始化相關,這部分也是android/window平臺區別最大的部分,注意這裡有個大坑,不同win32下,在建立視窗的執行緒下可以直接建立surface,在android下需要等到視窗的訊息APP_CMD_INIT_WINDOW裡才能建立surface,android裡的native activity初始化過程可以參考 Android——NativeActivity - C/C++ Apk開發,建立surface過程,選擇呈現/渲染通道以及同步呈現與渲染的物件,建立renderpass,然後根據surface建立交換鏈,根據交換鏈得到呈現image列表,根據image列表得到fbo列表用於附著到RenderPass上用於渲染,這裡選擇一種比較簡單的CommandBuffer記錄方式,就是有交換鏈有幾個image,就建立幾個對應的CommandBuffer記錄.
然後建立邏輯裝置,載入需要參與計算的輸入影象資源,一般來說,影象資源要使用compute shader,usage肯定要有VK_IMAGE_USAGE_STORAGE_BIT,而現在大部分硬體來說,線性 features不支援VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT,用於CPU 可以訪問的資源需要linera_tiling.簡單來說,compute shader要求的紋理,現在硬體上,CPU大部分不能直接訪問,這就要求一箇中轉,先建立一個CPU可以訪問的資源如vkbuffer,然後把資料匯入這個資源中,然後通過裝置資源間vkCmdCopyBufferToImage複製到原CPU不能訪問的GPU紋理上。然後建立一個compute shader要求的輸出紋理,對應一個UBO結構,這個UBO對應compute shader輸入輸出。載入轉化的spv檔案,生成對應的compute pipeline.
在這個需求裡,渲染命令不會每楨修改,所以我們完全可以在開始楨渲染前就填充CommandBuffer。
- 填充計算管線的CommandBuffer,簡單來說,就是執行上面的compute pipeline,並把輸出紋理layout改成VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL。
- 填充呈現渲染裡的CommandBuffer,根據交換鏈裡的image資料填充對應的每個CommandBuffer,簡單來說,就是把上面計算完成的紋理通過vkCmdBlitImage複製到當前呈現的那張vkimage中。
注意,這裡只是儲存了動作,相當於把action放入佇列中,並沒執行佇列,在這裡,所有在每楨執行前的邏輯已經處理完。
然後到每楨渲染,如上先等計算管線的fences來訊號,這表明GPU佇列中已經執行完成CommandBuffer,如下程式碼的computerCmd又變成可執行狀態。注意建立fences時需要先給訊號,不然第一次進入就會一起等待,並且fences需要手動reset.然後根據vkAcquireNextImageKHR得到的索引拿到呈現渲染的CommandBuffer,執行完成呈現出來,呈現與渲染的同步都在裝置GPU內,一般用VkSemaphore來同步,不同於vkFence,他用於gpu-gpu間的同步,自動reset.
void onPreDraw() { auto device = context->logicalDevice.device; vkWaitForFences(device, 1, &computerFence, VK_TRUE, UINT64_MAX); vkResetFences(device, 1, &computerFence); VkSubmitInfo submitInfo = {}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &context->computerCmd; VK_CHECK_RESULT( vkQueueSubmit(context->computeQueue, 1, &submitInfo, computerFence)); }
在window平臺測試完,參照vulkan的demo,新建一個android資料夾,設定其中的setings.gragle.
主build.gradle就和一般的一樣,在vkcs1目錄下的build.gradle新增externalNativeBuild的cmake路徑,設定好AndroidManifest.xml,如下圖。
然後就可以用android studio開啟這個資料夾,然後Sync Project with Gradle Files,就會補起成餘下內容,最後應該是如下結構。
正常來說,應該就可以在android studio安裝及除錯了。
最後說下在移植到android下遇到的一些坑。
- undefined symbol: ANativeActivity_onCreate 找不到,解決方法在CMake中新增set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
- vkCreateAndroidSurfaceKHR 類似Fatal signal 11 (SIGSEGV)錯誤。這是前面說的因為需要等到APP_CMD_INIT_WINDOW 訊息後,才能初始化surface.
- 在WIN平臺glsl轉的spv檔案可以直接在android上使用,而hlsl的不行,這裡不知是否有誤,測試不行。
- 1080P下16/16的結果不對,二種解決方案,一是使用32/8,滿足整除,但是需要影象滿足對應的長寬條件,二是divup,然後在shader裡傳入width/hight,檢查ThreadID.xy在width/hight範圍了,需要做if檢查,但是不限制影象長寬大小。
參考:
https://github.com/LunarG/VulkanSamples vulkan基本API用法例項
https://github.com/SaschaWillems/Vulkan vulkan進階demo.
https://www.zhihu.com/people/xiao-peng-you-38-21/posts 上面vulkan demo的講解。
Android——NativeActivity - C/C++ Apk