啥是饅頭(Metal)

啥是饅頭(Metal)

What's Metal

The Metal framework supports GPU-accelerated advanced 3D graphics rendering and data-parallel computation workloads.

  • Metal 框架是一套專門給圖形處理器(GPU)定制的API。它可以盡可能發(fā)揮GPU的3D圖形渲染以及并行數(shù)據計算能力此蜈。Metal 給開發(fā)者提供的是非常底層的可以操作到GPU的接口蜕便,并且,Metal 對數(shù)據的并行計算能力以及對資源的預編譯能力可以極大的減少CPU的負擔。所以 Metal 同時具備了 low-level 和 low-overhead 的特點口四。

Why Metal

Deprecation of OpenGL and OpenCL
Apps built using OpenGL and OpenCL will continue to run in macOS 10.14, but these legacy technologies are deprecated in macOS 10.14. Games and graphics-intensive apps that use OpenGL should now adopt Metal. Similarly, apps that use OpenCL for computational tasks should now adopt Metal and Metal Performance Shaders.

  • 在 MacOS 10.14 的更新文檔中,蘋果表示使用 OpenGL 和 OpenCL 構建的應用可以繼續(xù)在 macOS 10.14 中運行,但這些遺留技術在 macOS 10.14 中不推薦使用∽酱椋現(xiàn)在使用 OpenGL 的游戲和應用應轉向 Metal「竟福總體來說巾遭,蘋果爸爸已經表示要棄用 OpenGL/CL,并且推薦使用 Metal 作為替代闯估。

Where Metal

  • Metal 作為一個能夠高效地利用 GPU 對數(shù)據的并行處理能力以及對數(shù)據的圖形化接口灼舍,它可以解決很多由于高計算量帶來的問題。在機器學習涨薪、圖像視頻處理以及圖形渲染領域骑素,Metal 都能發(fā)揮出它的優(yōu)勢。

  • 當你遇到以下的情況時刚夺,Metal 也許是你最好的選擇:

  1. 你想要盡可能高效的渲染3D模型
  2. 你想要在處理圖像或者視頻的時候砂豌,類似對每一幀每一個像素進行數(shù)據集中處理的情況。
  3. 你碰到一些數(shù)據量很大的計算問題時光督,可以運用 Metal 的高并發(fā)處理能力阳距,將數(shù)據量分解為很多子數(shù)據集進行處理。
  4. 你想要在自己的游戲中制作一些獨特的效果结借,比如自定義 shading 和 lighting筐摘。

Hello Metal

在我們學習一門編程語言的時候,往往第一句代碼就是打印 "Hello world" 字符串船老。那么作為渲染框架的入門第一課咖熟,學會在界面上渲染出第一個三角形是最合適不過的了。

首先我們來介紹一下使用 Metal 來渲染一個模型的大致流程:
Initialize Metal -> Load Model -> Set up pipeline -> Render

直接上手柳畔,我們先從創(chuàng)建一個新的項目 HelloMetal 開始馍管,選擇iOS開發(fā)平臺,語言用 swift薪韩。

Initialize Metal

在 ViewController 中將 MetalKit 框架導入

import MetalKit

聲明 MTLDevice 屬性 device确沸,在 viewdidload 中初始化device。

var device: MTLDevice!
device = MTLCreateSystemDefaultDevice()

你可以理解 MTLDevice 為你和GPU的直接連接的一個抽象俘陷。你將通過使用 MTLDevice 創(chuàng)建所有其他你需要的Metal對象(像是command queues罗捎,buffers,textures)拉盾。

PS:注意如果是在 iOS 的模擬器環(huán)境下桨菜,是取不到 device 的

隨后初始化 MTKView 供顯示渲染后的圖像

let frame = CGRect(x: 0, y: 0, width: self.view.frame.size.width , height:self.view.frame.size.height)
let view = MTKView(frame: frame, device: device)
view.clearColor = MTLClearColor(red: 1, green: 1, blue: 0.8, alpha: 1)
self.view.addSubview(view)

MTKView 是 UIView 的一個子類,用于在 Metal 中展示渲染結果,同時提供一些方便的屬性和代理倒得。

設置 clearColor 使得 view 的默認背景被 clearcolor 填充泻红。

Load Model

由于現(xiàn)在要繪制的是一個平面三角形,所以這里簡單地 hardcode 三角形的頂點數(shù)據作為數(shù)據源霞掺,后續(xù)會介紹如何通過 Model I/O 框架來 load 基本 3D 模型谊路,以及加載 obj 模型。

首先添加聲明一個頂點的常量數(shù)組以及聲明 一個 MTLBuffer 變量
vertexBuffer根悼。

let vertexData: [Float] = [
        0.0, 1.0, 0.0,
        -1.0, -1.0, 0.0,
        1.0, -1.0, 0.0
    ]
    
var vertexBuffer: MTLBuffer!

然后在 viewdidload 中接著初始化 vertexBuffer

let dataSize = vertexData.count * MemoryLayout.size(ofValue: vertexData[0])
vertexBuffer = device.makeBuffer(bytes: vertexData, length: dataSize, options:[])

Set up pipeline

pipeline 渲染管線

在上手寫 pipeline 相關的代碼之前,我們先來簡單了解一下什么是 pipeline蜀撑,更加詳細的針對 pipeline 的解說會在后續(xù)教程中給出挤巡。

pipeline 就是渲染管線,是在渲染處理過程中順序執(zhí)行的一系列操作酷麦。這一套渲染流程在理論層面上都是統(tǒng)一的矿卑,所以不論是 OpenGL ES 的渲染管線還是 Metal 的渲染管線,在理解上都是相同的沃饶。pipeline 來源于生產車間的流水線作業(yè)母廷,在渲染過程中,一個操作接一個操作進行糊肤,就如同流水線一樣琴昆,這樣的實現(xiàn)可以極大地提高渲染效率。整個渲染管線如同下圖所示:

pipeline.png

渲染管線的大致流程為:頂點數(shù)據來源 -> 頂點著色器 -> 圖元裝配 ->
光柵化 -> 片元著色器 -> 拿到FrameBuffer

圖中標紅的 Vertex Proccesing 和 Fragment Proccessing 是可編程管線馆揉,一般是通過寫著色器語言(Shader Language)腳本實現(xiàn)业舍。在 Metal 中使用的 Metal Shading Language,同樣也是 C++ 的一個子集升酣。

queues,buffer and encoders

GPU 渲染出來的每一幀都是通過你發(fā)送給 GPU 的指令來生成的舷暮。在 Metal 中,每一幀的渲染我們都將用一個 render command encoder 包裹這些相關的指令噩茄。而 command buffer 是用于管理這些 encoders下面,再上一層, command queue 用于管理這些 command buffers绩聘。

在整個渲染過程中沥割,只需要創(chuàng)建一個 command queue 來管理 command buffers,以及上文提到過的 device凿菩、vertex buffer 也只需要創(chuàng)建一次驯遇。還有頂點著色器、片元著色器蓄髓、pipelineState 都是叉庐。需要多次創(chuàng)建的是那些和幀的變化具備強關聯(lián)的東西,比如 command buffer会喝,command encoder陡叠。每一幀的渲染都需要 encoder 去設置pipelineState玩郊,去設置 vertex buffer 以及繪制指令。

pipeline2.png

shader

shader 是運行在GPU上的腳本枉阵,它是 C++ 的一種子集語言译红。一般來說我們可以在 xcode 中創(chuàng)建 .metal 格式的 shader 腳本文件,但是其實直接在主文件中將 shader 以 string 的形式賦值保存也可以兴溜。以下就是兩個最簡單的 shader 函數(shù)侦厚,頂點處理器 vertex_main 以及片元處理器 fragment_main:

        let shader = """
#include <metal_stdlib>
using namespace metal;

vertex float4 vertex_main(constant packed_float3* vertex_array[[buffer(0)]],
                      unsigned int vid[[vertex_id]]) {
   return float4(vertex_array[vid], 1.0);
}

fragment float4 fragment_main() {
  return float4(0, 1, 0, 1);
}
"""

簡單來講,頂點處理器顧名思義就是對CPU傳輸過來的頂點數(shù)據做處理拙徽,當然也可以什么都不做刨沦,直接返回,就和這里的 vertex_main 一樣膘怕。而片元處理器是用來確定一個像素的著色想诅,它決定了像素的顏色表現(xiàn)。

然后我們通過這個 shader 的 string 或者 .metal 文件來初始化兩個函數(shù)岛心,并將它們設置給一個渲染管道描述器(renderPipelineDescriptor)来破,用于后續(xù)初始化 pipelineState。

 let library = try! device.makeLibrary(source: shader, options: nil)
        let vertexFunction = library.makeFunction(name: "vertex_main")
        let fragmentFunction = library.makeFunction(name: "fragment_main")
        

pipeline state

在 Metal 中忘古,我們需要給 GPU 設置渲染管線狀態(tài)徘禁,以此告訴 GPU 在 pipeline state 發(fā)生改變之前,其他的都不會有變化髓堪,從而使 GPU 的工作更加高效晌坤。pipelineState 包含了所有 GPU 需要知道的信息,包括像素格式以及剛剛創(chuàng)建的 shader 函數(shù)等旦袋。pipeline state 是通過一個 pipeline descriptor 創(chuàng)建的骤菠,我們可以通過設置 descriptor 的相關屬性來改變 pipeline state。

 let pipelineDescriptor = MTLRenderPipelineDescriptor()
 pipelineDescriptor.vertexFunction = vertexFunction
 pipelineDescriptor.fragmentFunction = fragmentFunction
 pipelineDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm

 pipelineState = try! device.makeRenderPipelineState(descriptor: pipelineDescriptor)

這里需要注意的一點是疤孕,創(chuàng)建一個 pipelineState 是耗時的操作商乎,所以我們應該一次性創(chuàng)建 pipelineState。在實際項目中祭阀,或許我們需要一次性創(chuàng)建多個 pipelineState 以調用不同的 shader 函數(shù)鹉戚,或者使用不同的頂點布局等等。

Render

終于到了渲染這步专控,從這一步開始抹凳,我們所寫的代碼針對的是每一幀的渲染,也就是每一幀都要調用這部分的代碼伦腐。

MTKView 的一個代理方法 public func draw(in view: MTKView) 會在每一幀繪制的時候進行調用赢底,所以一般來說,我們可以在這個代理中去繪制每一幀的內容。但是本節(jié)的需求只是繪制一個不會動的三角形幸冻,所以沒有必要每幀渲染粹庞,直接在 viewdidload 中接著往下寫。

guard let commandBuffer = commandQueue.makeCommandBuffer(),
let descriptor = view.currentRenderPassDescriptor,
let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: descriptor) else { 
             fatalError() 
             }
            

在這里洽损,我們通過 commandQueue 創(chuàng)建 commandBuffer庞溜。commandBuffer 中保存著這一幀中所有你需要讓 GPU 給你渲染的指令。
同時碑定,我們創(chuàng)建了一個 renderPassDescriptor流码,用于 commandEncoder 的創(chuàng)建。

接下來延刘,我們需要給 commandEncoder 設置當前的 pipelineState漫试,告訴 GPU 有關像素格式以及 shader 函數(shù)等信息已經包含在這個 pipelinestate 中了,在 state 發(fā)生改變之前访娶,以上的信息都不會有任何變化商虐,你放心地去處理渲染觉阅。

renderEncoder.setRenderPipelineState(pipelineState)

然后給 commandEncoder 設置頂點數(shù)據崖疤,這里的頂點數(shù)據就是上文創(chuàng)建的 vertexbuffer,告訴它需要處理的頂點數(shù)據來自哪里典勇。

renderEncoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0)

最后是要 draw 的部分了

renderEncoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 3, instanceCount: 1)

在這一步告訴 GPU 的是劫哼,去將那些頂點數(shù)據按照給出的頂點順序數(shù)目渲染成一個三角形。當然割笙,這一步也不是真正的渲染权烧,在 GPU 接收到所有的 commandbuffer 的指令之后,它才會去做真正的渲染過程伤溉。

//1
renderEncoder.endEncoding() 
//2
guard let drawable = view.currentDrawable else {
            fatalError()
        } 
// 3    
commandBuffer.present(drawable)
commandBuffer.commit()

步驟1 告訴 renderEncoder 已經沒有更多的指令了般码,步驟2 是從 MTKView 中拿到一個 CAMetalDrawable 類實例,這個 drawable 持有著一個可供 Metal 讀寫的可繪制 texture乱顾。步驟3 就是要求 commandBuffer 將指令提交給 GPU 并且將結果渲染展示到 drawable 上面板祝。這一步觸發(fā)了真正的渲染,編譯運行代碼可以看到在屏幕上出現(xiàn)了一個全屏的綠色三角形走净,而背景部分則是被 clearColor 覆蓋的米黃色券时。
如圖所示:

result.png

通過繪制一個簡單的三角形我們熟悉了 Metal 渲染的整體流程,這也是學習 Metal 的第一步而已伏伯,后續(xù)會繼續(xù)介紹更多有關 Metal橘洞、圖形學以及線代方面的東西。下一章主要介紹 3D 模型的渲染以及詳細的 render pipeline 渲染管線工作流程说搅。

Demo地址

點擊查看 Whats Metal 第一節(jié)Demo

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末炸枣,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌抛虏,老刑警劉巖博其,帶你破解...
    沈念sama閱讀 222,000評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異迂猴,居然都是意外死亡慕淡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評論 3 399
  • 文/潘曉璐 我一進店門沸毁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來峰髓,“玉大人,你說我怎么就攤上這事息尺⌒” “怎么了?”我有些...
    開封第一講書人閱讀 168,561評論 0 360
  • 文/不壞的土叔 我叫張陵搂誉,是天一觀的道長徐紧。 經常有香客問我,道長炭懊,這世上最難降的妖魔是什么并级? 我笑而不...
    開封第一講書人閱讀 59,782評論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮侮腹,結果婚禮上嘲碧,老公的妹妹穿的比我還像新娘。我一直安慰自己父阻,他們只是感情好愈涩,可當我...
    茶點故事閱讀 68,798評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著加矛,像睡著了一般履婉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上斟览,一...
    開封第一講書人閱讀 52,394評論 1 310
  • 那天毁腿,我揣著相機與錄音,去河邊找鬼趣惠。 笑死狸棍,一個胖子當著我的面吹牛,可吹牛的內容都是我干的味悄。 我是一名探鬼主播草戈,決...
    沈念sama閱讀 40,952評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼侍瑟!你這毒婦竟也來了唐片?” 一聲冷哼從身側響起丙猬,我...
    開封第一講書人閱讀 39,852評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎费韭,沒想到半個月后茧球,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 46,409評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡星持,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,483評論 3 341
  • 正文 我和宋清朗相戀三年抢埋,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片督暂。...
    茶點故事閱讀 40,615評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡揪垄,死狀恐怖,靈堂內的尸體忽然破棺而出逻翁,到底是詐尸還是另有隱情饥努,我是刑警寧澤,帶...
    沈念sama閱讀 36,303評論 5 350
  • 正文 年R本政府宣布八回,位于F島的核電站酷愧,受9級特大地震影響,放射性物質發(fā)生泄漏缠诅。R本人自食惡果不足惜溶浴,卻給世界環(huán)境...
    茶點故事閱讀 41,979評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望滴铅。 院中可真熱鬧戳葵,春花似錦就乓、人聲如沸汉匙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽噩翠。三九已至,卻和暖如春邦投,著一層夾襖步出監(jiān)牢的瞬間伤锚,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評論 1 272
  • 我被黑心中介騙來泰國打工志衣, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留屯援,地道東北人。 一個月前我還...
    沈念sama閱讀 49,041評論 3 377
  • 正文 我出身青樓念脯,卻偏偏與公主長得像狞洋,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子绿店,可洞房花燭夜當晚...
    茶點故事閱讀 45,630評論 2 359

推薦閱讀更多精彩內容