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。

  1. 填充計算管線的CommandBuffer,簡單來說,就是執行上面的compute pipeline,並把輸出紋理layout改成VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL。
  2. 填充呈現渲染里的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下遇到的一些坑。

  1. undefined symbol: ANativeActivity_onCreate 找不到,解決方法在CMake中添加set(CMAKE_SHARED_LINKER_FLAGS “${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate”)
  2. vkCreateAndroidSurfaceKHR 類似Fatal signal 11 (SIGSEGV)錯誤。這是前面說的因為需要等到APP_CMD_INIT_WINDOW 消息後,才能初始化surface.
  3. 在WIN平台glsl轉的spv文件可以直接在android上使用,而hlsl的不行,這裡不知是否有誤,測試不行。
  4. 1080P下16/16的結果不對,二種解決方案,一是使用32/8,滿足整除,但是需要影像滿足對應的長寬條件,二是divup,然後在shader里傳入width/hight,檢查ThreadID.xy在width/hight範圍了,需要做if檢查,但是不限制影像長寬大小。

  參考:

   vulkan基本API用法實例

   vulkan進階demo.

   上面vulkan demo的講解。

  Android——NativeActivity – C/C++ Apk開發