Metal基礎實踐

簡介

Metal 提供了和 GPU 的底層交互咙咽,讓開發(fā)者可以使自己的iOS、macOS淤年、tvOS應用表現(xiàn)出最佳的圖形和運算處理性能钧敞。該文章從蘋果官方提供的機組 demo 入手,介紹 MetalKit 和 Metal Shading Language 的使用麸粮,并使用它們進行圖形渲染和科學計算溉苛。

MetalKit使用的基本步驟

第一個最重要的類是 MTKView ,這是一個包裹了 UIView 或者 NSView 的對象,具備 Metal-spcific 的核心動畫功能弄诲,渲染的內(nèi)容在 MTKView 上進行顯示愚战。MTKView 比較重要的屬性是 preferredFramesPerSecond 、device 和 delegate 齐遵。

preferredFramesPerSecond 毋庸置疑是用來設置幀率的凤巨,這個幀率不是絕對的,會受限與設備的最大幀率和最小幀率洛搀,當這個值大于最大幀率敢茁,則選擇最大幀率;小于最小幀率留美,則選擇最小幀率彰檬。其默認值為 60 伸刃。

device 用來獲取 Metal 與 GPU 交互的一系列對象,默認值是 nil 逢倍,需要使用
MTLCreateSystemDefaultDevice() 來主動獲取捧颅。

delegate 具備了 MTKViewDelegate ,其提供 mtkView:drawableSizeWillChange: 和 drawInMTKView: 回調(diào)较雕。每刷新一幀 drawInMTKView: 回調(diào)會被調(diào)用一次碉哑,在 drawInMTKView: 里面可以進行繪制或者計算相關的工作;mtkView:drawableSizeWillChange: 回調(diào)是在 MTKView 的 frame 發(fā)生改變的時候回調(diào)亮蒋,可以手機發(fā)生屏幕旋轉(zhuǎn)或者其他需要調(diào)整視圖的操作時扣典,調(diào)整繪制區(qū)域。

第二類是 Metal 與 GPU 交互相關的對象
如圖是一個基本繪制的流程:

繪制流程

我們需要一個具備 MTLCommandQueue 協(xié)議的對象慎玖,其負責在每一幀里生產(chǎn)一系列具備 MTLCommandBuffer 協(xié)議的對象贮尖,Metal 與 GPU 的交互都會被寫入到這些具備 MTLCommandBuffer 協(xié)議的對象里面,而這個寫入的過程需要通過一個具備 MTLXXXCommandEncoder(MTLRenderCommandEncoder趁怔、MTLComputeCommandEncoder 或者其他的Encoder)協(xié)議的對象湿硝。

以下是一個繪制的基本結構:

- (void)prepare {
    _device = MTLCreateSystemDefaultDevice();
    _commandQueue = [_device newCommandQueue];
}

- (void)drawInMTKView:(nonnull MTKView *)view
{
    // 設置背景顏色
    Color color = [self makeFancyColor];
    view.clearColor = MTLClearColorMake(color.red, color.green, color.blue, color.alpha);

    id<MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];
    commandBuffer.label = @"MyCommand";

    MTLRenderPassDescriptor *renderPassDescriptor = view.currentRenderPassDescriptor;
    if(renderPassDescriptor != nil)
    {
        id<MTLRenderCommandEncoder> renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];

        renderEncoder.label = @"MyRenderEncoder";

    /*
    這里寫入繪制相關
    */

        [renderEncoder endEncoding];

        [commandBuffer presentDrawable:view.currentDrawable];
    }

    // Finalize rendering here and submit the command buffer to the GPU
    [commandBuffer commit];
}

并行計算的 MTLComputeCommandEncoder 對象不需要借助 MTLRenderPassDescriptor 來創(chuàng)建,其結構為:

- (void)prepare {
    _device = MTLCreateSystemDefaultDevice();
    _commandQueue = [_device newCommandQueue];
}

- (void)drawInMTKView:(nonnull MTKView *)view
{
    id<MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];
    commandBuffer.label = @"MyCommand";

    id<MTLComputeCommandEncoder> computeEncoder = [commandBuffer computeCommandEncoder];

     /*
        并行計算相關
     */   

    [computeEncoder endEncoding];
    
    /*
        有其他的 Encoder 可以繼續(xù)疊加
    */

    // Finalize rendering here and submit the command buffer to the GPU
    [commandBuffer commit];
}

繪制一個三角形

繪制管線是 GPU 處理圖像渲染的一步步流程润努,如圖所示:

繪制管線

Metal 的繪制管線包含了 Vertex function 关斜、Rasterization、Fragment function 三個階段铺浇,Vertex function 階段接受頂點數(shù)據(jù)(這里的頂點數(shù)據(jù)包括了頂點的位置和顏色信息)痢畜,負責將頂點數(shù)據(jù)繪制到一個 2D 的可視區(qū)域;Rasterization 接受從 Vertex function 傳過來的頂點數(shù)據(jù)随抠,決定哪些數(shù)據(jù)是要繪制在什么地方的裁着;Fragment function 將顏色值賦值到像素(后期如果有紋理繁涂,也在這里操作)拱她,最后輸出繪制后的圖像。創(chuàng)建一個繪制管線的步驟如下:

MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
pipelineStateDescriptor.label = @"Simple Pipeline";
pipelineStateDescriptor.vertexFunction = vertexFunction; // 設置 Vertex function
pipelineStateDescriptor.fragmentFunction = fragmentFunction; // 設置 Fragment function
pipelineStateDescriptor.colorAttachments[0].pixelFormat = mtkView.colorPixelFormat; // 像素顏色格式
    
// 使用的時候?qū)?_pipelineState 賦值給 Encoder
_pipelineState = [_device newRenderPipelineStateWithDescriptor:pipelineStateDescriptor
                                                            error:&error];
                                                                

其中 Vertex function 和 Fragment function 分別在頂點著色器和片段著色器中實現(xiàn)扔罪,需要使用 Metal Shading Language 來編寫秉沼,Rasterization 階段不提供編程化的接口。

Metal Shading Language 的語法和 C++ 14 很像矿酵,區(qū)別在于 C++ 14 是 CPU 上運行的語言唬复,Metal Shading Language 運行在 GPU 上,GPU 提供更大并行處理能力全肮,對有大量數(shù)據(jù)需要處理的 Vertex function 和 Fragment function 會大大的提高效率敞咧。編寫的著色器函數(shù)保存為 .metal 文件,其編譯分為兩個階段:

1. Front-end 階段發(fā)生在 XCode build 時辜腺,.metal 文件會被編譯為 IR 文件休建。
2. Back-end 階段發(fā)生在 runtime 乍恐,IR 文件會被編譯為機器碼。

加載 IR 文件的過程通過 device 的 newDefaultLibrary 方法测砂,得到一個具備 MTLLibrary 協(xié)議的對象茵烈。取出里面的 Vertex function 和 Fragment function 通過 MTLLibrary 對象的 newFunctionWithName 方法。

id<MTLLibrary> defaultLibrary = [_device newDefaultLibrary];

id<MTLFunction> vertexFunction = [defaultLibrary newFunctionWithName:@"vertexShader"]; // 得到 Vertex function
id<MTLFunction> fragmentFunction = [defaultLibrary newFunctionWithName:@"fragmentShader"]; // 得到 Fragment function

繪制使用的數(shù)據(jù)類型涉及 SIMD 和一個具備 MTLBuffer 協(xié)議的對象砌些,SIMD 是一個獨立于 Metal 的庫呜投,能簡化算法和 GPU 處理流程,效率高使用十分方便存璃,常在 Metal 應用中使用仑荐。MTLBuffer 是 Metal 提供的用來保存大量頂點數(shù)據(jù)的 buffer ,其內(nèi)存由 GPU 可訪問的內(nèi)存分配有巧,效率高释漆,可以節(jié)省內(nèi)存的占用(當頂點數(shù)據(jù)量龐大的時候)。使用的時候一般自定義一個頂點數(shù)據(jù)結構篮迎,包含坐標點和顏色信息男图,這個數(shù)據(jù)結構的定義會用到 SIMD 里面的類型:

typedef struct
{
    vector_float2 position;// 一個二維的位置矢量

    vector_float4 color;// 一個四維的顏色矢量
} AAPLVertex;

將定義好的 APPLVertex 數(shù)組傳入 MTLBuffer 對象可以通過:

// Set up a simple MTLBuffer with our vertices which include texture coordinates
static const AAPLVertex quadVertices[] =
{
    // Pixel positions, Texture coordinates
    { {  250,  -250 },  { 1.f, 0.f } },
    { { -250,  -250 },  { 0.f, 0.f } },
    { { -250,   250 },  { 0.f, 1.f } },

    { {  250,  -250 },  { 1.f, 0.f } },
    { { -250,   250 },  { 0.f, 1.f } },
    { {  250,   250 },  { 1.f, 1.f } },
};

// Create our vertex buffer, and initialize it with our quadVertices array
_vertices = [_device newBufferWithBytes:quadVertices
                                    length:sizeof(quadVertices)
                                options:MTLResourceStorageModeShared];

之后使用 Encoder 的 setVertexBuffer: 方法可將 MTLBuffer 對象傳給頂點著色器(Vertex function),運行繪制管線甜橱。

最終的繪制的三角形如圖:

三角形

參考

Metal 文檔

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末逊笆,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子岂傲,更是在濱河造成了極大的恐慌难裆,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,919評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件镊掖,死亡現(xiàn)場離奇詭異乃戈,居然都是意外死亡,警方通過查閱死者的電腦和手機亩进,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,567評論 3 392
  • 文/潘曉璐 我一進店門症虑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人归薛,你說我怎么就攤上這事谍憔。” “怎么了主籍?”我有些...
    開封第一講書人閱讀 163,316評論 0 353
  • 文/不壞的土叔 我叫張陵习贫,是天一觀的道長。 經(jīng)常有香客問我千元,道長苫昌,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,294評論 1 292
  • 正文 為了忘掉前任幸海,我火速辦了婚禮祟身,結果婚禮上屋厘,老公的妹妹穿的比我還像新娘。我一直安慰自己月而,他們只是感情好汗洒,可當我...
    茶點故事閱讀 67,318評論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著父款,像睡著了一般溢谤。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上憨攒,一...
    開封第一講書人閱讀 51,245評論 1 299
  • 那天世杀,我揣著相機與錄音,去河邊找鬼肝集。 笑死瞻坝,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的杏瞻。 我是一名探鬼主播所刀,決...
    沈念sama閱讀 40,120評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼捞挥!你這毒婦竟也來了浮创?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,964評論 0 275
  • 序言:老撾萬榮一對情侶失蹤砌函,失蹤者是張志新(化名)和其女友劉穎斩披,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體讹俊,經(jīng)...
    沈念sama閱讀 45,376評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡垦沉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,592評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了仍劈。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片厕倍。...
    茶點故事閱讀 39,764評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖耳奕,靈堂內(nèi)的尸體忽然破棺而出绑青,到底是詐尸還是另有隱情诬像,我是刑警寧澤屋群,帶...
    沈念sama閱讀 35,460評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站坏挠,受9級特大地震影響芍躏,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜降狠,卻給世界環(huán)境...
    茶點故事閱讀 41,070評論 3 327
  • 文/蒙蒙 一对竣、第九天 我趴在偏房一處隱蔽的房頂上張望庇楞。 院中可真熱鬧,春花似錦否纬、人聲如沸吕晌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,697評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽睛驳。三九已至,卻和暖如春膜廊,著一層夾襖步出監(jiān)牢的瞬間乏沸,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,846評論 1 269
  • 我被黑心中介騙來泰國打工爪瓜, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蹬跃,地道東北人。 一個月前我還...
    沈念sama閱讀 47,819評論 2 370
  • 正文 我出身青樓铆铆,卻偏偏與公主長得像蝶缀,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子薄货,可洞房花燭夜當晚...
    茶點故事閱讀 44,665評論 2 354