深入理解iOS GPU加速框架Metal及MPS

     iOS的Metal框架是一個類似OpenGL的框架,通過編寫shaders(類c代碼)運行在GPU上,利用GPU的高並行能力執行並行操作,比如圖像處理,卷積神經網絡。而MPS就是一套基於Metal框架的庫,用戶不需要理解Metal的細節,直接調用這些庫即可使用高性能處理能力。同時針對卷積神經網絡,官方已經提供了MPSCnn庫,開發人員可以直接使用該庫組件神經網絡即可以在移動端iPhone執行神經網絡模型,比如圖片識別,速度是槓槓的,很多年前,作者曾經利用該庫和自己自定義的一些算子實現了實時圖片風格化,人物自動摳圖等功能,在iPhone上FPS能達到50fps,可見這性能有多好。接下來我們來詳細分析下Metal框架及MPS。

OpenGLES

    由於Metal和OpenGLES類似,我們先介紹大家更加熟悉的OpenGL

 

 

這個圖裏開發者需要重點了解3個模塊

 

 

  • 頂點着色器

        開發者用來自定義頂點信息(座標和顏色),是一段代碼。Vertex shader就是調整頂點信息,比如透視圖變換,Camera位置調整。有多少個頂點就會調用多少次,如果頂點較少,這裏執行較慢的話,性能影響不大。

  • 柵格化

    • 簡單來說,比如你告訴GL我想畫條線,然後告訴它線的端點座標是(0,0)和(0,100),那麼GL自動腦補出中間100個點的座標,這個過程就叫柵格化(光柵化),腦補的方法叫線性差值.
    • 複雜點,現在我要畫個三角形,給他三個頂點的座標,它會計算這個三角形裏面的所有像素座標。
    • 再複雜點,不僅僅給頂點座標,還告訴他(0, 0)座標點是白色,(0,100)點是黑色,那麼柵格化就自動計算出中間100個點每個點的顏色,自動做過渡的效果,  這個計算方法還是線性差值。
  • 片段着色器

        開發者根據柵格化出來的具體位置信息用來繪製顏色,也是一段代碼。由於是並行處理,這裏的邏輯儘量保持併發友好。Frage shader就是柵格化之後,根據輸入的柵格化座標/顏色信息自己再加工處理輸出一個顏色,比如光照,霧化處理等等。每個片段(像素)會執行一次,這裏執行較慢的話,性能影響比較大(線程不夠用的情況下)。

 

Metal

 

    Metal和Opengl類似,但是同時Metal進一步擴展並支持了更多GPU驅動各種命令,所以儘管iOS側也可以使用OpenGl, 但是官方還是推薦大家使用Metal, 且Metal由於更豐富的接口和功能,利用Metal可更加方便高效的進行非傳統的圖形數據處理。

基本元素

 

MTLDevice

    GPU Device,Metal 中提供了 MTLDevice 的接口,代表了 GPU。

device = MTLCreateSystemDefaultDevice()

MTLCommandQueue

    確定GPU設備後我們需要一個渲染隊列 MTLCommandQueue,該隊列是單一隊列,確保了指令能夠順序執行,裏面保存的是將要渲染的指令MTLCommandBuffer,這是個線程安全的隊列,可以支持多個 CommandBuffer 同時編碼。
 

device.makeCommandQueue()

MTLCommandBuffer

 

 

commandQueue.makeCommandBuffer()

MTLCommandEncoder

    目前Metal支持4種Encoder

  • MTLRenderCommandEncoder(2個shader)

  • 這種是最常見的,就是圖形繪製命令。會包含兩個shader, 定點shader和片段shader

  • MTLComputeCommandEncoder(1個shader)

  • 這種就是存儲並行計算命令,這種只有computer shader

  • MTLBlitCommandEncoder(0個shader)

  • 這是一種簡單數據處理命令,比如圖片縮放,旋轉角度等簡單的數據處理複製,這個其實在普通應用也有見過的, 比如Android系統通過底層dma操作實現blit加速操作,這種操作不需要shader。

這三種的創建函數

 

PipeLineState

    PipeLineState包含了執行的一些狀態信息,比如執行的函數,線程信息(maxTotalThreadsPerThreadgroup)。不同的Encoder,有不同的PipeLineState,

  • MTLRenderPipelineState

  • MTLComputePipelineState

MTLLibrary

    我們知道前面的各種Encoder是可能需各種shader,shader就是代碼,因而需要給他們指定代碼函數, 這些Function都是通過MTLLibrary加載的。通過指定shader程序庫文件創建

device.makeLibrary(filepath: path)

MTLFunction

    指定shader文件裏的函數並創建MTLFunction

library.makeFunction(name: name)

圖像處理示例

    Texture紋理座標是2d座標,頂點座標是3維(z爲0時2d),對應模型的座標,一個頂點座標對應一個紋理座標。

 

    drawIndexedPrimitives(glDrawArray)相當於加載內置的vertex/fragment shader程序。如果是更復雜的繪製(比如繪製3d,添加各種效果等等),需要通過自定義的shader來處理,主要是修改vertexFunction, fragmentFunction及給這些函數傳參數

 

shader函數賦值

    上圖的vertex shader是直接獲取Buffer裏的數據,並沒有做任何改動。vertexFunc的返回數據結構可自定義,但是fragmentFunc的返回值就是顏色信息。上面的Vertex通過指定了buffer的index來獲取數據, 還是有些耦合的,可以通過更靈活的方式來定義,比如attribute標識,然後只要在Swift側通過VertexDescriptor來定義這些attribute的屬性,即可綁定這些參數的來源

 

Instance-norm示例

    瞭解過深度學習應該都瞭解instance-norm,這個算子的核心工作就是求平均值,然後算方差。並行求平均值可以分解爲多個小範圍求和,然後再將這些和相加得到總值,然後再除上總量即可獲得平均值。這個算法如果利用圖像柵格化後每個片段着色器來執行求和是不好處理的,因爲並行的執行體間沒法共享變量,且由於並行的執行單元並沒有嚴格的順序關係,誰來負責最後的總值計算也是一個很大的問題。Metal通過compute shader可以很好地完成這個任務(OpenGL 4.3也引入了Compute Shader )。核心是線程組內的線程間內存共享和同步機制,下面我們來看實例,由於需要共享數據,只能有一組threadGroup。

所以上面的第一個線程負責1, 257, 1025等像素的處理,第二線程負責2, 258, 1026等像素的處理,最後會彙總爲256個和。最後選一個線程繼續彙總爲總和即可,當然可以再並行,比如256->32->1這種結果。

這裏的thread等屬性只適合Compute Shader,不適合Render shader。


/**************************************************
* 本文來自CSDN博主"一點碼客",喜歡請頂部點擊關注
* 轉載請標明出處:http://blog.csdn.net/itchosen
***************************************************/

如需實時查看更多更新文章,請關注公衆號"一點碼客",一起探索技術

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章