GPUImage移植總結


項目github地址: aoce

我是去年年底才知道有GPUImage這個項目,以前也一直沒有在移動平台開發過,但是我在win平台有編寫一個類似的項目oeip(不要關注了,所有功能都移植或快移植到aoce里了),移動平台是大勢所趨,開始是想着把oeip移植到android平台上,后面發現不現實,就直接重開項目,從頭開始,從Vulkan到CMake,再到GPUImage,開發主力平台也從Visual Studio 2017換到VSCode了,這也算是前半年的總結了.

Vulkan移植GPUImage的安卓Demo展示

Vulkan移植GPUImage(一)高斯模糊與自適應閾值

Vulkan移植GPUImage(二)Harris角點檢測與導向濾波

Vulkan移植GPUImage(三)從A到C的濾鏡

Vulkan移植GPUImage(四)從D到O的濾鏡

Vulkan移植GPUImage(五)從P到Z的濾鏡

CMake 常用命令

在Android用vulkan完成藍綠幕扣像

android下vulkan與opengles紋理互通

Vulkan與DX11交互

使用Swig轉換C++到別的編程語言

PC的Vulkan運算層時間計算記錄

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數據處理流程

我定義主要實現要滿足二點.

  1. 計算流程可以多個輸入/輸出,每個節點可以多個輸入輸出,每個節點可以關閉打開,也可關閉打開此節點分支.

  2. 別的用戶能非常容易擴展自己的功能,就是自定義圖像處理層的功能.

第一點,我受FrameGraph|設計&基於DX12實現啟發,想到利用有向無環圖來實現.在開始構建時,節點互相連接好.然后利用深度優先搜索自動排除到關閉自己/分支的節點,拿到最終的有效連接線,有向無環圖可以根據有效連接線生成正確的執行順序,然后檢查每層節點與連接的節點的圖像類型是否符合,檢查成功后就初始化每層節點的資源,如果是Vulkan模塊,所有層資源生成后,就會把所有執行命令填充到當前圖層的VkCommandBuffer對象中,運行時執行VkCommandBuffer記錄的指令.

在運行時,設定節點/分支是否可用,以及有些層參數改變會影響輸出大小都會導致圖層重啟標記開啟,用標記是考慮到更新參數層與執行GPU運算不在同一線程的情況,圖層下次運行前,檢測到重啟標記開啟,就會重新開始構建.

相關源碼在PipeGraph

而第二點,為了方便用戶擴展自己的層,我需要盡可能的自動完善各種信息來讓用戶只專注需求實現.

對於運算層基類(BaseLayer)注意如下幾個時序.

  1. onInit/onInitNode 當層被加入PipeGraph前/后分別調用,在之后,會有個弱引用關聯PipeGraph的PipeNode,同樣,檢查這個引用是否有效可以知道是否已經附加到PipeGraph上.

  2. onInitLayer 當PipeGraph生成正確的有效連接線后,根據有效連接線重新連接上下層並生成正確執行順序后,對各運算層調用.

  3. onInitBuffer 每層輸入檢查對應連接層的輸出的圖像類型是否符合.

  4. onFrame 每楨運行時調用.

  5. onUpdateParamet 層的參數更新,時序獨立於上面的4個點,設定要求隨時可以調用.

相關源碼在BaseLayer

准確到Vulkan模塊,Vulkan下的運算層基類(VkLayer)會針對BaseLayer提供更精確的Vulkan資源時序.

  1. 初始化,一般指定使用的shader路徑,UBO大小,更新UBO內數據.默認認為一個輸入,一個輸出,如果是多輸入與多輸出,可以在這指定.注意輸入/輸出個數一定要在附加在PipeGraph之前確定,相應數組會根據這二個值生成空間.

  2. onInitGraph,當vklayer被添加到VkPipeGraph時上被調用.一般用來加載shader,根據輸入與輸出個數生成pipelineLayout,如果有自己邏輯,請override.默認指定輸入輸出的的圖像格式為rgba8,如果不是,請在這指定對應圖像格式.如果層內包含別的處理層邏輯,請在這添上別的處理層.

  3. onInitNode,當onInitGraph后被添加到PipeGraph后調用.本身layer在onInitGraph后,onInitNode前添加到PipeGraph了,當層內包含別的層時,用來指定層內之間的數據如何鏈接.

  4. onInitLayer,當PipeGraph根據連接線重新構建正確的執行順序后.根據各層是否啟用等,PipeGraph構建正確的各層執行順序,在這里,每層都知道對應層數據的輸入輸出層,也知道輸入輸出層的大小.當前層的輸入大小默認等於第0個輸入層的輸出大小,並指定線程組的分配大小,如果邏輯需要變化,請在這里修改.

  5. onInitVkBuffer,當所有有效層執行完后onInitLayer后,各層開始調用onInitBuffer,其在自動查找到輸入層的輸出Texture,並生成本層的輸出Texture給當前層的輸出使用后調用.如果自己有Vulkan Buffer需要處理,請在onInitVkBuffer里處理.

  6. onInitPipe,當本層執行完onInitVkBuffer后調用,在這里,根據輸入與輸出的Texture自動更新VkWriteDescriptorSet,並且生成ComputePipeline.如果有自己的邏輯,請override實現.

  7. onCommand 當所有層執行完onInitBuffer后,填充vkCommandBuffer,vkCmdBindPipeline/vkCmdBindDescriptorSets/vkCmdDispatch 三件套.

  8. 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,然后根據需求重載上面的一二個函數就行了,歡迎大家在這基礎之上實現自己的運算層.

框架數據流程

數據提供現主要包含如下三種.

  1. 攝像頭,在win端,有aoce_win_mf模塊提供,在android端,有aoce_android提供.

  2. 對於多媒體文件(本地多媒體,RTMP等),由aoce_ffmpeg(win/android都支持)提供解碼.

  3. 直接非壓縮的圖像二進制數據.

數據處理模塊現有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頭文件給外部程序使用,這些頭文件主要包含如下三種類型.

  1. C風格的結構,C風格導出幫助函數,與C風格導出用來創建對應工廠/管理對象.

  2. 純凈的抽像類,不包含任何STL對象結構,主要用來調用API,用戶不要繼承這些類.

  3. 后綴為Observer的抽像類,用戶繼承針對接口處理回調.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM