iOS Metal初識

一.Metal 簡介

????????在?WWDC?2014 上格了,Apple為游戲開發(fā)者推出了新的平臺技術(shù) Metal,該技術(shù)能夠為 3D 圖像提高 10 倍的渲染性能挤巡,并支持大家熟悉的游戲引擎及公司。

????????Metal 是一種低層次的渲染應用程序編程接口酷麦,提供了軟件所需的最低層矿卑,保證軟件可以運行在不同的圖形芯片上。Metal 提升了 A7 與 A8 處理器效能沃饶,讓其性能完全發(fā)揮母廷。

? ? ? ? Metal,充分利用GPU的運算能力糊肤,在現(xiàn)階段琴昆,AVFoundation ?臉識別/.... 等大量需要顯示計算的時候,蘋果采用了硬件加速器驅(qū)動GPU工作馆揉,在音視頻方面业舍,?頻編碼/解碼 / 視頻編碼/解碼 ->壓縮任務 ->都與硬件加速器分不開,蘋果提供的Metal升酣,能發(fā)揮GPU/CPu的最大性能舷暮,并且管理我們的資源。

二.Metal的渲染流程

????????Metal的渲染流程借鑒了OpenGLES的流程噩茄,它通過控制頂點著色器/片元著色器(Metal里面叫頂點函數(shù)/片元函數(shù))下面,交給幀緩沖區(qū),最后顯示到屏幕上

Metal渲染管道流程

? ? ? ? 值得注意的是绩聘,在OpenGlES中沥割,圖元裝配有9中耗啦,在Metal中,圖元裝配只有五種机杜,他們分別是:

?? ? ? ? ????????MTLPrimitiveTypePoint = 0, 點

?? ? ? ? ????????MTLPrimitiveTypeLine = 1, 線段

?? ? ? ????????? MTLPrimitiveTypeLineStrip = 2, 線環(huán)

?? ? ? ????????? MTLPrimitiveTypeTriangle = 3,? 三角形

?? ? ? ? ????????MTLPrimitiveTypeTriangleStrip = 4, 三角型扇

三.Metal的初級準備工作

3.1Metal的注意事項

????????在講Metal的初級使用之前芹彬,我們先來看看蘋果爸爸給我們的建議,首先叉庐,蘋果建議我們Separate Your Rendering Loop舒帮,即分離我們渲染,Metal給我們提供了一個View陡叠,叫MTKView玩郊,它繼承自UiView,它主要的渲染是通過MTKViewDelegate協(xié)議回調(diào)實現(xiàn)枉阵,兩個重要的協(xié)議方法是:

????????1)當MTKView視圖發(fā)生大小改變時調(diào)用

????????/*!

?????????@method mtkView:drawableSizeWillChange:

?????????@abstract Called whenever the drawableSize of the view will change

?????????@discussion Delegate can recompute view and projection matricies or regenerate any buffers to be compatible with the new view size or resolution

?????????@paramviewMTKView which called this method

?????????@paramsizeNew drawable size in pixels

?????????*/

- (void)mtkView:(nonnull MTKView *)view drawableSizeWillChange:(CGSize)size;

? ? ? ? 2)每當視圖需要渲染時調(diào)用

????????/*!

?????????@method drawInMTKView:

?????????@abstract Called on the delegate when it is asked to render into the view

?????????@discussion Called on the delegate when it is asked to render into the view

?????????*/

????????- (void)drawInMTKView:(nonnullMTKView*)view;

? ??3.2 ?Metal是如何驅(qū)動GPU工作的译红?

? ??????

Metal驅(qū)動GPU工作

????????相關(guān)對應代碼:在ViewController中,我們把當前的View變成MTKView兴溜,當然你也可以用self.view添加一個子視圖View侦厚,CCRenderer是自定義的一個類,主要是分離MTview的渲染拙徽,

?? ??????_view.device = MTLCreateSystemDefaultDevice();一個MTLDevice 對象就代表這著一個GPU,通常我們可以調(diào)用方法MTLCreateSystemDefaultDevice()來獲取代表默認的GPU單個對象.

????????在CCRenderer中的初始化方法中- (id)initWithMetalKitView:(MTKView *)mtkView我們拿到device刨沦,創(chuàng)建newCommandQueue隊列:

? ??????????????_commandQueue = [_device newCommandQueue];

? ??????所有應用程序需要與GPU交互的第一個對象是一個對象->MTLCommandQueue.?你使用MTLCommandQueue 去創(chuàng)建對象,并且加入MTLCommandBuffer 對象中.確保它們能夠按照正確順序發(fā)送到GPU.對于每一幀,一個新的MTLCommandBuffer 對象創(chuàng)建并且填滿了由GPU執(zhí)行的命令.

? ??????在CCRenderer中,我們實現(xiàn)了MTKView的協(xié)議代理方法膘怕,在- (void)drawInMTKView:(nonnullMTKView*)view中想诅,我們通過創(chuàng)建好的隊列再創(chuàng)建命令緩沖區(qū)并且加入到MTCommandBuffer對象中去:

? ??????????????id<MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];

????????值得注意的是,在創(chuàng)建好命令緩沖區(qū)后岛心,Metal提出了一個概念叫渲染描述符:(個人理解這個渲染描述符是給每個命令打上一個標記来破,GPU在工作的時候通過這個渲染描述符取出相應的命令,如果說的不對忘古,請大神指點)從視圖繪制中,獲得渲染描述符:

? ??????????????MTLRenderPassDescriptor *renderPassDescriptor = view.currentRenderPassDescriptor;

通過渲染描述符renderPassDescriptor創(chuàng)建MTLRenderCommandEncoder? ? ? ? ? ? ? ??

? ??????????????id<MTLRenderCommandEncoder> renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];

? ? ? ? 最后?[renderEncoderendEncoding];

當編碼器結(jié)束之后,命令緩存區(qū)就會接受到2個命令.

?? ? ? ? 1) present

?? ? ? ? 2) commit

?? ? ? ? 因為GPU是不會直接繪制到屏幕上,因此你不給出去指令.是不會有任何內(nèi)容渲染到屏幕上.

????????[commandBuffer presentDrawable:view.currentDrawable];

? ??????[commandBuffercommit];

????????至此徘禁,Metal的準備工作已經(jīng)完成

四.用Metal渲染一個簡單的三角形

在做好上面的準備的準備工作后:

渲染三角形流程

//初始化MTKView

- (nonnull instancetype)initWithMetalKitView:(nonnull MTKView *)mtkView

{

? ? self= [superinit];

? ? if(self)

? ? {

? ? ? ? NSError*error =NULL;


? ? ? ? //1.獲取GPU 設(shè)備

? ? ? ? _device= mtkView.device;

? ? ? ? //2.在項目中加載所有的(.metal)著色器文件

? ? ? ? // 從bundle中獲取.metal文件

? ? ? ? id defaultLibrary = [_devicenewDefaultLibrary];

? ? ? ? //從庫中加載頂點函數(shù)

? ? ? ? id vertexFunction = [defaultLibrarynewFunctionWithName:@"vertexShader"];

? ? ? ? //從庫中加載片元函數(shù)

? ? ? ? id fragmentFunction = [defaultLibrarynewFunctionWithName:@"fragmentShader"];

? ? ? ? //3.配置用于創(chuàng)建管道狀態(tài)的管道

? ? ? ? MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init];

? ? ? ? //管道名稱

? ? ? ? pipelineStateDescriptor.label=@"Simple Pipeline";

? ? ? ? //可編程函數(shù),用于處理渲染過程中的各個頂點

? ? ? ? pipelineStateDescriptor.vertexFunction= vertexFunction;

? ? ? ? //可編程函數(shù),用于處理渲染過程中各個片段/片元

? ? ? ? pipelineStateDescriptor.fragmentFunction= fragmentFunction;

? ? ? ? //一組存儲顏色數(shù)據(jù)的組件

? ? ? ? pipelineStateDescriptor.colorAttachments[0].pixelFormat= mtkView.colorPixelFormat;


? ? ? ? //4.同步創(chuàng)建并返回渲染管線狀態(tài)對象

? ? ? ? _pipelineState= [_devicenewRenderPipelineStateWithDescriptor:pipelineStateDescriptorerror:&error];

? ? ? ? //判斷是否返回了管線狀態(tài)對象

? ? ? ? if (!_pipelineState)

? ? ? ? {


? ? ? ? ? ? //如果我們沒有正確設(shè)置管道描述符,則管道狀態(tài)創(chuàng)建可能失敗

? ? ? ? ? ? NSLog(@"Failed to created pipeline state, error %@", error);

? ? ? ? ? ? returnnil;

? ? ? ? }

? ? ? ? //5.創(chuàng)建命令隊列

? ? ? ? _commandQueue = [_device newCommandQueue];

? ? }

? ? return self;

}

//每當視圖需要渲染幀時調(diào)用

- (void)drawInMTKView:(nonnullMTKView*)view

{

? ? //1. 頂點數(shù)據(jù)/顏色數(shù)據(jù)

? ? staticconstCCVertextriangleVertices[] =

? ? {

? ? ? ? //頂點,? ? RGBA 顏色值

? ? ? ? { {? 0.5, -0.25,0.0,1.0}, {1,0,0,1} },

? ? ? ? { { -0.5, -0.25,0.0,1.0}, {0,1,0,1} },

? ? ? ? { { -0.0f,0.25,0.0,1.0}, {0,0,1,1} },

? ? };

? ? //2.為當前渲染的每個渲染傳遞創(chuàng)建一個新的命令緩沖區(qū)

? ? id<MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];

? ? //指定緩存區(qū)名稱

? ? commandBuffer.label=@"MyCommand";


? ? //3.

? ? // MTLRenderPassDescriptor:一組渲染目標髓堪,用作渲染通道生成的像素的輸出目標送朱。

? ? MTLRenderPassDescriptor *renderPassDescriptor = view.currentRenderPassDescriptor;

? ? //判斷渲染目標是否為空

? ? if(renderPassDescriptor !=nil)

? ? {

? ? ? ? //4.創(chuàng)建渲染命令編碼器,這樣我們才可以渲染到something

? ? ? ? id renderEncoder =[commandBufferrenderCommandEncoderWithDescriptor:renderPassDescriptor];

? ? ? ? //渲染器名稱

? ? ? ? renderEncoder.label=@"MyRenderEncoder";

? ? ? ? //5.設(shè)置我們繪制的可繪制區(qū)域

? ? ? ? /*

? ? ? ? typedef struct {

? ? ? ? ? ? double originX, originY, width, height, znear, zfar;

? ? ? ? } MTLViewport;

?? ? ? ? */

? ? ? ? //視口指定Metal渲染內(nèi)容的drawable區(qū)域。 視口是具有x和y偏移旦袋,寬度和高度以及近和遠平面的3D區(qū)域

? ? ? ? //為管道分配自定義視口需要通過調(diào)用setViewport:方法將MTLViewport結(jié)構(gòu)編碼為渲染命令編碼器骤菠。 如果未指定視口,Metal會設(shè)置一個默認視口疤孕,其大小與用于創(chuàng)建渲染命令編碼器的drawable相同商乎。

? ? ? ? MTLViewportviewPort = {

? ? ? ? ? ? 0.0,0.0,_viewportSize.x,_viewportSize.y,-1.0,1.0

? ? ? ? };

? ? ? ? [renderEncodersetViewport:viewPort];

? ? ? ? //[renderEncoder setViewport:(MTLViewport){0.0, 0.0, _viewportSize.x, _viewportSize.y, -1.0, 1.0 }];


? ? ? ? //6.設(shè)置當前渲染管道狀態(tài)對象

? ? ? ? [renderEncodersetRenderPipelineState:_pipelineState];



? ? ? ? //7.從應用程序OC 代碼 中發(fā)送數(shù)據(jù)給Metal 頂點著色器 函數(shù)

? ? ? ? //頂點數(shù)據(jù)+顏色數(shù)據(jù)

? ? ? ? //? 1) 指向要傳遞給著色器的內(nèi)存的指針

? ? ? ? //? 2) 我們想要傳遞的數(shù)據(jù)的內(nèi)存大小

? ? ? ? //? 3)一個整數(shù)索引,它對應于我們的“vertexShader”函數(shù)中的緩沖區(qū)屬性限定符的索引祭阀。

? ? ? ? [renderEncodersetVertexBytes:triangleVertices

?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? length:sizeof(triangleVertices)

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? atIndex:CCVertexInputIndexVertices];

? ? ? ? //viewPortSize 數(shù)據(jù)

? ? ? ? //1) 發(fā)送到頂點著色函數(shù)中,視圖大小

? ? ? ? //2) 視圖大小內(nèi)存空間大小

? ? ? ? //3) 對應的索引

? ? ? ? [renderEncodersetVertexBytes:&_viewportSize

?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? length:sizeof(_viewportSize)

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? atIndex:CCVertexInputIndexViewportSize];



? ? ? ? //8.畫出三角形的3個頂點

? ? ? ? // @method drawPrimitives:vertexStart:vertexCount:

? ? ? ? //@brief 在不使用索引列表的情況下,繪制圖元

? ? ? ? //@param 繪制圖形組裝的基元類型

? ? ? ? //@param 從哪個位置數(shù)據(jù)開始繪制,一般為0

? ? ? ? //@param 每個圖元的頂點個數(shù),繪制的圖型頂點數(shù)量

? ? ? ? /*

?? ? ? ? MTLPrimitiveTypePoint = 0, 點

?? ? ? ? MTLPrimitiveTypeLine = 1, 線段

?? ? ? ? MTLPrimitiveTypeLineStrip = 2, 線環(huán)

?? ? ? ? MTLPrimitiveTypeTriangle = 3,? 三角形

?? ? ? ? MTLPrimitiveTypeTriangleStrip = 4, 三角型扇

?? ? ? ? */


? ? ? ? [renderEncoderdrawPrimitives:MTLPrimitiveTypeTriangle

? ? ? ? ? ? ? ? ? ? ? ? ? vertexStart:0

? ? ? ? ? ? ? ? ? ? ? ? ? vertexCount:3];

? ? ? ? //9.表示已該編碼器生成的命令都已完成,并且從NTLCommandBuffer中分離

? ? ? ? [renderEncoderendEncoding];

? ? ? ? //10.一旦框架緩沖區(qū)完成鲜戒,使用當前可繪制的進度表

? ? ? ? [commandBufferpresentDrawable:view.currentDrawable];

? ? }

? ? //11.最后,在這里完成渲染并將命令緩沖區(qū)推送到GPU

? ? [commandBuffercommit];

}


效果圖

Metal文件:(語法下篇介紹)

#include

//使用命名空間 Metal

using namespace metal;

// 導入Metal shader 代碼和執(zhí)行Metal API命令的C代碼之間共享的頭

#import "CCShaderTypes.h"

// 頂點著色器輸出和片段著色器輸入

//結(jié)構(gòu)體

typedef struct

{

? ? //處理空間的頂點信息

? ? float4clipSpacePosition [[position]];

? ? //顏色

? ? float4color;

} RasterizerData;

//頂點著色函數(shù)

vertex RasterizerData

vertexShader(uintvertexID [[vertex_id]],

?? ? ? ? ? ? constantCCVertex*vertices [[buffer(CCVertexInputIndexVertices)]],

?? ? ? ? ? ? constantvector_uint2*viewportSizePointer [[buffer(CCVertexInputIndexViewportSize)]])

{

? ? /*

?? ? 處理頂點數(shù)據(jù):

? ? ? ? 1) 執(zhí)行坐標系轉(zhuǎn)換,將生成的頂點剪輯空間寫入到返回值中.

? ? ? ? 2) 將頂點顏色值傳遞給返回值

?? ? */


? ? //定義out

? ? RasterizerDataout;?

//? ? //初始化輸出剪輯空間位置

//? ? out.clipSpacePosition = vector_float4(0.0, 0.0, 0.0, 1.0);

//

//? ? // 索引到我們的數(shù)組位置以獲得當前頂點

//? ? // 我們的位置是在像素維度中指定的.

//? ? float2 pixelSpacePosition = vertices[vertexID].position.xy;

//

//? ? //將vierportSizePointer 從verctor_uint2 轉(zhuǎn)換為vector_float2 類型

//? ? vector_float2 viewportSize = vector_float2(*viewportSizePointer);

//

//? ? //每個頂點著色器的輸出位置在剪輯空間中(也稱為歸一化設(shè)備坐標空間,NDC),剪輯空間中的(-1,-1)表示視口的左下角,而(1,1)表示視口的右上角.

//? ? //計算和寫入 XY值到我們的剪輯空間的位置.為了從像素空間中的位置轉(zhuǎn)換到剪輯空間的位置,我們將像素坐標除以視口的大小的一半.

//? ? out.clipSpacePosition.xy = pixelSpacePosition / (viewportSize / 2.0);

? ? out.clipSpacePosition= vertices[vertexID].position;

? ? //把我們輸入的顏色直接賦值給輸出顏色. 這個值將于構(gòu)成三角形的頂點的其他顏色值插值,從而為我們片段著色器中的每個片段生成顏色值.

? ? out.color= vertices[vertexID].color;

? ? //完成! 將結(jié)構(gòu)體傳遞到管道中下一個階段:

? ? returnout;

}

//當頂點函數(shù)執(zhí)行3次,三角形的每個頂點執(zhí)行一次后,則執(zhí)行管道中的下一個階段.柵格化/光柵化.

// 片元函數(shù)

//[[stage_in]],片元著色函數(shù)使用的單個片元輸入數(shù)據(jù)是由頂點著色函數(shù)輸出.然后經(jīng)過光柵化生成的.單個片元輸入函數(shù)數(shù)據(jù)可以使用"[[stage_in]]"屬性修飾符.

//一個頂點著色函數(shù)可以讀取單個頂點的輸入數(shù)據(jù),這些輸入數(shù)據(jù)存儲于參數(shù)傳遞的緩存中,使用頂點和實例ID在這些緩存中尋址.讀取到單個頂點的數(shù)據(jù).另外,單個頂點輸入數(shù)據(jù)也可以通過使用"[[stage_in]]"屬性修飾符的產(chǎn)生傳遞給頂點著色函數(shù).

//被stage_in 修飾的結(jié)構(gòu)體的成員不能是如下這些.Packed vectors 緊密填充類型向量,matrices 矩陣,structs 結(jié)構(gòu)體,references or pointers to type 某類型的引用或指針. arrays,vectors,matrices 標量,向量,矩陣數(shù)組.

fragmentfloat4fragmentShader(RasterizerDatain [[stage_in]])

{

? ? //返回輸入的片元顏色

? ? returnin.color;

}

用于OC和Metal橋接的文件:

/*

?介紹:

?頭文件包含了 Metal shaders 與C/OBJC 源之間共享的類型和枚舉常數(shù)

*/

#ifndef CCShaderTypes_h

#define CCShaderTypes_h

// 緩存區(qū)索引值 共享與 shader 和 C 代碼 為了確保Metal Shader緩存區(qū)索引能夠匹配 Metal API Buffer 設(shè)置的集合調(diào)用

typedef enum CCVertexInputIndex

{

? ? //頂點

? ? CCVertexInputIndexVertices? ? =0,

? ? //視圖大小

? ? CCVertexInputIndexViewportSize =1,

} CCVertexInputIndex;

//結(jié)構(gòu)體: 頂點/顏色值

typedef struct

{

? ? // 像素空間的位置

? ? // 像素中心點(100,100)

? ? vector_float4 position;

? ? // RGBA顏色

? ? vector_float4 color;

} CCVertex;

#endif

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末庞溜,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子延刘,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件割笙,死亡現(xiàn)場離奇詭異般码,居然都是意外死亡走净,警方通過查閱死者的電腦和手機橘洞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來霍衫,“玉大人,你說我怎么就攤上這事∠⒊撸” “怎么了徐紧?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵侮腹,是天一觀的道長加矛。 經(jīng)常有香客問我毁腿,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任狸棍,我火速辦了婚禮身害,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘草戈。我一直安慰自己塌鸯,他們只是感情好,可當我...
    茶點故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布唐片。 她就那樣靜靜地躺著丙猬,像睡著了一般。 火紅的嫁衣襯著肌膚如雪费韭。 梳的紋絲不亂的頭發(fā)上茧球,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天,我揣著相機與錄音星持,去河邊找鬼抢埋。 笑死,一個胖子當著我的面吹牛督暂,可吹牛的內(nèi)容都是我干的揪垄。 我是一名探鬼主播,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼逻翁,長吁一口氣:“原來是場噩夢啊……” “哼饥努!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起八回,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤酷愧,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后缠诅,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體溶浴,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年管引,在試婚紗的時候發(fā)現(xiàn)自己被綠了戳葵。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,919評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡汉匙,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出生蚁,到底是詐尸還是另有隱情噩翠,我是刑警寧澤,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布邦投,位于F島的核電站伤锚,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏志衣。R本人自食惡果不足惜屯援,卻給世界環(huán)境...
    茶點故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一猛们、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧狞洋,春花似錦弯淘、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至借嗽,卻和暖如春态鳖,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背恶导。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工浆竭, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人惨寿。 一個月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓邦泄,卻偏偏與公主長得像,于是被迫代替她去往敵國和親缤沦。 傳聞我的和親對象是個殘疾皇子虎韵,可洞房花燭夜當晚...
    茶點故事閱讀 44,864評論 2 354