一.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ū),最后顯示到屏幕上
? ? ? ? 值得注意的是绩聘,在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工作的译红?
? ??????
????????相關(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