項目github地址: aoce
我是去年年底才知道有GPUImage這個項目,以前也一直沒有在移動平台開發過,但是我在win平台有編寫一個類似的項目oeip(不要關注了,所有功能都移植或快移植到aoce里了),移動平台是大勢所趨,開始是想着把oeip移植到android平台上,后面發現不現實,就直接重開項目,從頭開始,從Vulkan到CMake,再到GPUImage,開發主力平台也從Visual Studio 2017換到VSCode了,這也算是前半年的總結了.
Vulkan移植GPUImage(二)Harris角點檢測與導向濾波
Vulkan移植GPUImage的Compute Shader總目錄
選擇Vulkan的Compute Shader處理管線
當初選擇Vulkan,一是越來越多設備與平台支持,且有獨立的計算管線.
獨立的計算管線在移植GPUImage里時好處如下.
1 避免很多UV生成類,如GPUImage里的GPUImageTwoInputFilter / GPUImageTwoInputCrossTextureSamplingFilter等等這種要么多個輸入,要么需要查找周圍點來生成不同UV,特別還有多個輸入與需要周邊UV結合,導致其中GPUImage中有很多類就是用來給FS提供UV.
2 不需要一個對應Vulkan渲染輸出窗口,簡單來說,你可以無窗口運行計算流程,並把結果直接對接win平台GUI32/DX11的CPU輸出/GPU紋理,也可以在android中對接opengl es紋理,也可以方便對接引擎UE4/Unity3D.
3 計算管線可以利用局部共享顯存,局部共享顯存在那種需要查找周邊多個點的情況能大幅提高性能,原則上來說,CS比渲染管線少PS之前的那一系列階段,最新的硬件應該會比用VS+PS高吧?我用vulkan/cuda/dx11(原oeip實現)比較了下運行復雜計算管線的情況,cuda的GPU占比最低,vulkan其次,dx11會在cuda/vulkan的二倍以上.
不過缺點也有,其中有三個沒移植GPUImage的功能,其中二個就是畫多條線的,主要就是利用VS/PS渲染管線完成,暫時還沒想出好的方法移植,還有一個圖像2D-3D多角度轉換利用VS/PS渲染管線也很方便,不過這個在獨立的計算管線應該也好做.
Vulkan數據處理流程
我定義主要實現要滿足二點.
-
計算流程可以多個輸入/輸出,每個節點可以多個輸入輸出,每個節點可以關閉打開,也可關閉打開此節點分支.
-
別的用戶能非常容易擴展自己的功能,就是自定義圖像處理層的功能.
第一點,我受FrameGraph|設計&基於DX12實現啟發,想到利用有向無環圖來實現.在開始構建時,節點互相連接好.然后利用深度優先搜索自動排除到關閉自己/分支的節點,拿到最終的有效連接線,有向無環圖可以根據有效連接線生成正確的執行順序,然后檢查每層節點與連接的節點的圖像類型是否符合,檢查成功后就初始化每層節點的資源,如果是Vulkan模塊,所有層資源生成后,就會把所有執行命令填充到當前圖層的VkCommandBuffer對象中,運行時執行VkCommandBuffer記錄的指令.
在運行時,設定節點/分支是否可用,以及有些層參數改變會影響輸出大小都會導致圖層重啟標記開啟,用標記是考慮到更新參數層與執行GPU運算不在同一線程的情況,圖層下次運行前,檢測到重啟標記開啟,就會重新開始構建.
相關源碼在PipeGraph
而第二點,為了方便用戶擴展自己的層,我需要盡可能的自動完善各種信息來讓用戶只專注需求實現.
對於運算層基類(BaseLayer)注意如下幾個時序.
-
onInit/onInitNode 當層被加入PipeGraph前/后分別調用,在之后,會有個弱引用關聯PipeGraph的PipeNode,同樣,檢查這個引用是否有效可以知道是否已經附加到PipeGraph上.
-
onInitLayer 當PipeGraph生成正確的有效連接線后,根據有效連接線重新連接上下層並生成正確執行順序后,對各運算層調用.
-
onInitBuffer 每層輸入檢查對應連接層的輸出的圖像類型是否符合.
-
onFrame 每楨運行時調用.
-
onUpdateParamet 層的參數更新,時序獨立於上面的4個點,設定要求隨時可以調用.
相關源碼在BaseLayer
准確到Vulkan模塊,Vulkan下的運算層基類(VkLayer)會針對BaseLayer提供更精確的Vulkan資源時序.
-
初始化,一般指定使用的shader路徑,UBO大小,更新UBO內數據.默認認為一個輸入,一個輸出,如果是多輸入與多輸出,可以在這指定.注意輸入/輸出個數一定要在附加在PipeGraph之前確定,相應數組會根據這二個值生成空間.
-
onInitGraph,當vklayer被添加到VkPipeGraph時上被調用.一般用來加載shader,根據輸入與輸出個數生成pipelineLayout,如果有自己邏輯,請override.默認指定輸入輸出的的圖像格式為rgba8,如果不是,請在這指定對應圖像格式.如果層內包含別的處理層邏輯,請在這添上別的處理層.
-
onInitNode,當onInitGraph后被添加到PipeGraph后調用.本身layer在onInitGraph后,onInitNode前添加到PipeGraph了,當層內包含別的層時,用來指定層內之間的數據如何鏈接.
-
onInitLayer,當PipeGraph根據連接線重新構建正確的執行順序后.根據各層是否啟用等,PipeGraph構建正確的各層執行順序,在這里,每層都知道對應層數據的輸入輸出層,也知道輸入輸出層的大小.當前層的輸入大小默認等於第0個輸入層的輸出大小,並指定線程組的分配大小,如果邏輯需要變化,請在這里修改.
-
onInitVkBuffer,當所有有效層執行完后onInitLayer后,各層開始調用onInitBuffer,其在自動查找到輸入層的輸出Texture,並生成本層的輸出Texture給當前層的輸出使用后調用.如果自己有Vulkan Buffer需要處理,請在onInitVkBuffer里處理.
-
onInitPipe,當本層執行完onInitVkBuffer后調用,在這里,根據輸入與輸出的Texture自動更新VkWriteDescriptorSet,並且生成ComputePipeline.如果有自己的邏輯,請override實現.
-
onCommand 當所有層執行完onInitBuffer后,填充vkCommandBuffer,vkCmdBindPipeline/vkCmdBindDescriptorSets/vkCmdDispatch 三件套.
-
onFrame 每楨處理時調用,一般來說,只有輸入層或輸出層override處理,用於把vulkan texture交給CPU/opengl es/dx11等等.
相關源碼在VkLayer
雖然列出有這么多,但是從我移植GPUImage里來看,很多層特別是混合模式那些處理,完全一個都不用重載,就只在初始化指定下glslPath就行了,還有許多層按上面設定只需要重載一到二個方法就不用管了.
其中Vulkan圖層中,每個圖層中包含一個VulkanContext對象,其有獨立的VkCommandBuffer對象,這樣可以保證每個圖層在多個線程互不干擾,各個線程可以獨立運行一個或是多個圖層,對於cuda圖層來說,每個圖層也有個cudaStream_t對象,做到各個線程獨立運行.
其中aoce_vulkan我定義了VkPipeGraph/VkLayer的實現,以及各個Vulkan對象的封裝,還有輸入/輸出,包含RGBA<->YUV的轉化這些基本的計算層,余下的GPUImage的所有層全在aoce_vulkan_extra完成,也算是對方便用戶擴展自己的層的一個測試,說實話,在移植GPUImage到aoce_vulkan_extra模塊過程中,我感覺以前存儲的一些Vulkan知識已經快被我忘光了.
最后到這,用戶實現自己的vulkan處理層,就不需要懂太多vulkan知識就能完成,只需要寫好glsl源碼,繼承VkLayer,然后根據需求重載上面的一二個函數就行了,歡迎大家在這基礎之上實現自己的運算層.
框架數據流程
數據提供現主要包含如下三種.
-
攝像頭,在win端,有aoce_win_mf模塊提供,在android端,有aoce_android提供.
-
對於多媒體文件(本地多媒體,RTMP等),由aoce_ffmpeg(win/android都支持)提供解碼.
-
直接非壓縮的圖像二進制數據.
數據處理模塊現有aoce_cuda/aoce_vulkan模塊處理,win端現支持這二個模塊,而android端只支持aoce_vulkan模塊.
如果數據提供的是楨數據,對應攝像頭/多媒體模塊都會解析到VideoFrame並給出回調,而在數據處理模塊會有InputLayer層,專門用來接收上面三種數據.
而處理后數據會根據對應OutputLayer需要,導出CPU數據以及GPU數據對接對應系統常用渲染引擎對應紋理上,如在win端,aoce_cuda/aoce_vulkan模塊的OutputLayer都支持直接導致到對應DX11紋理,而在android上,aoce_vulkan能直接導致到對應opengl es紋理上,這樣就能直接與對應引擎(UE4/Unity3D)底層進行對接.
導出給用戶調用
在重新整理了框架與結構,完善了一些內容,API應該不會有大的變動了,現開始考慮外部用戶使用.
在框架各模塊內部,引用導出的類不要求什么不能用STL,畢竟肯定你編譯這些模塊肯定是相同編譯環境,但是如果導出給別的用戶使用,需要限制導出的數據與格式,以保證別的用戶與你不同的編譯環境也不會有問題.
配合CMake,使用install只導出特殊編寫的.h頭文件給外部程序使用,這些頭文件主要包含如下三種類型.
-
C風格的結構,C風格導出幫助函數,與C風格導出用來創建對應工廠/管理對象.
-
純凈的抽像類,不包含任何STL對象結構,主要用來調用API,用戶不要繼承這些類.
-
后綴為Observer的抽像類,用戶繼承針對接口處理回調.