Metal 翻譯的一塌糊塗

前言

Metal是一個爲腎系列量GPU量身定作的框架。名字是根據iOS平臺最底層的圖形處理框架命名出來的。編程

這套框架的兩個主題:3D圖形渲染以及並行計算。緩存

給誰用

跟虛幻/Unity對比扯皮Metal的強大,潛力(略)安全

對比OpenGL/OpenGLES, 教程相對簡單地Metal在腎平臺的圖形渲染優化程度作的比上述二者好。數據結構

最後下個結論在iOS系統,Metal是不二選擇。框架

優點

最大得優點是Metal輕量級了(對比OpenGL)。不管什麼時候你使用OpenGL在建立一個緩存或者紋理的時候,老是要進行拷貝動做避免GPU對他們進行操做。爲了安全考慮大量進行資源拷貝的代價顯而易見是巨大的。而Metal則不用進行這種拷貝的動做。由開發者來負責同步CPU與GPU的讀寫。運氣還不錯的是,大蘋果提供了其餘一些很棒的API--GCD,咱們能夠用這些API使同步更加容易。函數

Metal另一個優點提供了GPU狀態預判來避免大量的校驗與編譯。通常來講,若是你使用OpenGL你須要不斷的設置GPU的狀態,而且在畫圖以前須要進行狀態集校驗。最差得狀況下要從新編譯着色工廠(XD,不知道怎麼翻譯shader)並以此來獲取新的狀態。固然校驗的步驟是必須的,可是Metal選擇另一種方案。//重要一句,暫放。。。(在渲染引擎初始化階段,狀態集合被提取到預校驗渲染值。。。)佈局

API

Metal不少API以協議的方式提供。緣由是具體類型的Metal對象須要依賴其具體的機器型號。這也是爲了適應接口編程而不是實現編程。這也意味着你不用在繼承Metal類或者添加擴展以及去使用runtime的風險了。(做者很不自信啊。。。)優化

Metal爲了速度某種程度上犧牲了安全性。舉個栗子,你收到了一個指向內部緩存的空指針,這時候你的操做須要額外當心。當OpenGL這類場景出錯的時候一般是黑屏。Metal的話多是隨機錯誤,跳屏或者崩潰都是妥妥的。動畫

坑爹的是模擬器不支持。。。(這句話早說出來會被打吧,還有就是我是4S機器不支持硬件都不支持。。)ui

基礎編程

首先能夠到Github下個DEMO。

根據設備建立相應的UI接口

在Metal中,設備是GPU的抽象。咱們能夠經過MTLCreateSystemDefaultDevice方法來獲取當前設備。

id<MTLDevice> device = MTLCreateSystemDefaultDevice();

注意到返回值是一個泛型類,可是遵循了MTLDevice協議。

接下來得代碼片斷展現建立一個Metal層而且添加了一個UIView的背景層。

CAMetalLayer *metalLayer = [CAMetalLayer layer];
metalLayer.device = device;
metalLayer.pixelFormat = MTLPixelFormatBGRA8Unorm;
metalLayer.frame = view.bounds;
[view.layer addSublayer:self.metalLayer];

CAMetalLayerCALayer的一個子類用來展現Metal圖層緩存中的內容。咱們須要告知Layer層哪個設備咱們在用,而且告知要使用的像素格式。示例中咱們選擇了8位BGRA格式。

庫與函數

先提到Metal shaders用Metal shading語言來寫。

Metal庫是一堆函數集合。全部你去實現的shader方法將會被編譯進默認的庫。你能夠如此檢索到:

id<MTLLibrary> library = [device newDefaultLibrary]

咱們將在構建渲染流水線狀態的時候使用到這些庫。

命令隊列

指令提交給Metal設備經過相關得指令隊列。指令隊列獲取指令是線程安全的而且在設備上串行執行。以下建立一個執行指令:

id<MTLLibrary> library = [device newDefaultLibrary]

構建流水線

當咱們說起Metal編程中的流水線時候,咱們一般是指渲染時頂點矩陣的變換。頂點shaders與區域shaders是流水線可編程的關節,可是同時還有其餘會發生的事情(剪切,放大, 觀察點變換)不受咱們控制。後者流水線等功能組成了咱們的固定防水層流水線。

爲了獲取該方程,咱們根據加方法名字來從庫中獲取:

id<MTLFunction> vertexProgram = [library newFunctionWithName:@"vertex_function"];
id<MTLFunction> fragmentProgram = [library newFunctionWithName:@"fragment_function"];

咱們建立了一個pipeline來配置這些方法以及像素格式設置:

MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
[pipelineStateDescriptor setVertexFunction:vertexProgram];
[pipelineStateDescriptor setFragmentFunction:fragmentProgram];
pipelineStateDescriptor.colorAttachments[0].pixelFormat = MTLPixelFormatBGRA8Unorm;

最後咱們根據描述建立流水線自身的狀態。(略一句)

id<MTLRenderPipelineState> pipelineState = [device newRenderPipelineStateWithDescriptor:pipelineStateDescriptor error:nil];

加載數據到緩存

咱們設置好流水線後,咱們須要將數據填入進去。在示例工程中,咱們畫一個簡單地幾何圖形:快速轉動的方體。這個方體包含了共享一條邊的兩個正三角形。

static float quadVertexData[] =
{
     0.5, -0.5, 0.0, 1.0,     1.0, 0.0, 0.0, 1.0,
    -0.5, -0.5, 0.0, 1.0,     0.0, 1.0, 0.0, 1.0,
    -0.5,  0.5, 0.0, 1.0,     0.0, 0.0, 1.0, 1.0,

     0.5,  0.5, 0.0, 1.0,     1.0, 1.0, 0.0, 1.0,
     0.5, -0.5, 0.0, 1.0,     1.0, 0.0, 0.0, 1.0,
    -0.5,  0.5, 0.0, 1.0,     0.0, 0.0, 1.0, 1.0,
};

每行的前四個數字表明瞭x,y,z,w的組成值。後四個數字表明瞭紅色,綠色,藍色,還有透明值的組成值。

你可能感到奇怪用四個份量來表示3D空間。第四個組成點w爲了讓咱們在作3D變換(翻轉、移位、縮放)時候作統一處理提供了一個數學計算的便捷。

爲了用Metal畫出頂點數據,咱們須要將它放置在緩存中。緩存是一個簡單無結構化被CPU與GPU共享的少許內存。

vertexBuffer = [device newBufferWithBytes:quadVertexData
                                   length:sizeof(quadVertexData)
                                  options:MTLResourceOptionCPUCacheModeDef

咱們用另一個塊緩存去存儲旋轉後的矩陣。與提供數據更新不一樣,咱們只須要提供足夠長度的緩存空間。

uniformBuffer = [device newBufferWithLength:sizeof(Uniforms) 
                                    options:MTLResourceOptionCPUCacheModeDefault];

動畫

爲了在屏幕上旋轉方體,咱們須要變換頂點座標成頂點着色的一部分。這須要每一幀都更新統一的緩存。爲了達成這點,咱們運用三角學根據當前的旋轉角度來生成旋轉矩陣,並將旋轉矩陣拷貝到統一的緩存中。

統一的數據結構有單一的方法,該方法是一個44的矩陣保存着旋轉矩陣,其類型是在蘋果SIMD庫定義的浮點型44矩陣。該數據類型優點是能夠進行數據並行操做。

typedef struct
{
    matrix_float4x4 rotation_matrix;
} Uniforms;

爲了將旋轉矩陣拷貝到統一的緩存中,咱們獲取到其內存首地址並調用memcpy方法將矩陣拷入。

Uniforms uniforms;
uniforms.rotation_matrix = rotation_matrix_2d(rotationAngle);
void *bufferPointer = [uniformBuffer contents];
memcpy(bufferPointer, &uniforms, sizeof(Uniforms));

開始繪圖

爲了繪製Metal圖層,咱們首先要從圖層獲取‘可繪製的’部分。可繪製的對象管理着一套適合渲染的紋理:

id<CAMetalDrawable> drawable = [metalLayer nextDrawable];

接下來,咱們建立一個渲染過程描述,該描述用來講明Metal在執行渲染先後所須要得操做。以下面代碼,咱們描述的一個渲染過程首先會將幀緩存清空成一個白色固體,而後執行繪製操做,最後將結果存儲到幀緩存中用來展現:

MTLRenderPassDescriptor *renderPassDescriptor = [MTLRenderPassDescriptor renderPassDescriptor];
renderPassDescriptor.colorAttachments[0].texture = drawable.texture;
renderPassDescriptor.colorAttachments[0].loadAction = MTLLoadActionClear;
renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(1, 1, 1, 1);
renderPassDescriptor.colorAttachments[0].storeAction = MTLStoreActionStore;

繪製調用問題

放置指令到命令隊列的時候,指令必須被編碼到命令緩存中。一套命令緩存含有一個或者多個指令用來被執行和被緊湊的編碼成GPU識別的指令:

id<MTLCommandBuffer> commandBuffer = [self.commandQueue commandBuffer];

爲了實際編碼渲染指令,咱們須要另一個對象去了解怎麼將咱們的繪製函數調用轉換成GPU語言。這個對象被成爲command encoder。咱們經過想指令緩存申請編碼者和傳遞上述咱們建立的渲染過程描述符來建立它。

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

在繪製調用即將開始以前,咱們根據預編譯流水線狀態配置渲染指令編碼以及建立好緩存,這些都是頂點着色器的參數。

[renderEncoder setRenderPipelineState:pipelineState];
[renderEncoder setVertexBuffer:vertexBuffer offset:0 atIndex:0];
[renderEncoder setVertexBuffer:uniformBuffer offset:0 atIndex:1];

爲了着實執行幾何畫圖,咱們須要告知Metal咱們須要畫的圖是啥形狀,以及咱們要從緩存中消費多少頂點。

[renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:6];

最後告訴編碼器,咱們已經處理完畫圖調用,結束編碼:

[renderEncoder endEncoding];

展現幀緩存

如今咱們的繪圖指令已經編碼好並準備執行,咱們須要告知指令緩存來在屏幕上顯示結果。爲了達成這點,咱們根據從Metal層獲取到當前能夠繪製的對象調用presentDrawable

[commandBuffer presentDrawable:drawable];

爲了告知緩存已經準備就緒執行了,咱們須要進行確認:

[commandBuffer commit];

大概如此。。!

Metal Shading語言

基礎是C++11,限制了一些特性,添加了一些關鍵字。。。

實戰

爲了從咱們得着色器裏面取出頂點數據,咱們頂一個數據結構用來與OC中的頂點佈局數據進行交互。

typedef struct
{
    float4 position;
    float4 color;
} VertexIn;

咱們也須要一個很是相近的類型用來描述要從咱們頂點着色器頂點傳到局部着色器得數據類型。可是,這種狀況下咱們必需要肯定(根據使用[[position]]屬性)究竟是哪個數據結構成員須要被認定爲頂點位置:

typedef struct {
 float4 position [[position]];
 float4 color;
} VertexOut;

根據頂點數據中的每一個頂點都會執行一次頂點函數。它獲取執行頂點列表的指針與含有旋轉矩陣的統一數據的引用。第三個參數是當前操做頂點的索引。

須要注意的是屬性後得頂點函數的參數已經能說明它們的用途。上述狀況的緩存參數索引值與當設置渲染指定編碼器緩存時候咱們指定的索引值相匹配。這正是Metal怎麼計算出與緩存相對應得參數。

在定點方程內,咱們用頂點座標乘以旋轉矩陣。咱們將變換過的座標信息賦值給輸出頂點。頂點的顏色從輸入到輸出採用直接拷貝。

vertex VertexOut vertex_function(device VertexIn *vertices [[buffer(0)]],
                                 constant Uniforms &uniforms [[buffer(1)]],
                                 uint vid [[vertex_id]])
{
    VertexOut out;
    out.position = uniforms.rotation_matrix * vertices[vid].position;
    out.color = vertices[vid].color;
    return out;
}

對於每個像素來講這片斷方式都被執行一次。在簡單得片斷方程中,咱們只是簡單地傳遞由Metal生成的插入顏色。這就是屏幕上所顯示出來的像素顏色:

fragment float4 fragment_function(VertexOut in [[stage_in]])
{
    return in.color;
}

爲啥不用OpenGL擴展?

大蘋果是OpenGL考慮的平臺,而且OpenGL一直都爲iOS提供對應的擴展框架。可是從內部改變OpenGL彷佛應該是一個困難的任務由於它須要兼容多平臺的緣由(不可能定製)。儘管OpenGL一直在前進,可是進度很慢而且效果微乎其微。

另外一方面Metal是大蘋果平臺專屬工做。儘管API使用協議看上去怪怪得,可是它用起來仍是蠻不錯的。Metal使用OC寫的,以Fundation爲基礎,並使用了GCD來同步CPU和GPU。對比OpenGL它更高度抽象了GPU的流水線能夠作到不用徹底的重寫。

Mac上的Metal?

應該尚未整好。。。(略)

總結

總結了現狀是不少人還用不上Metal,可是高級碼農已經開始使用而且從中獲益。 若是你想徹底發揮硬件的潛力,Metal可讓開發者在遊戲中創造出獨一無二效果,或是更加快速的並行計算,讓他們(產品)更具備競爭力。

相關文章
相關標籤/搜索