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选择另一种方案。//重要一句,暂放。。。(在渲染引擎初始化阶段,状态集合被提取到预校验渲染值。。。)布局
Metal不少API以协议的方式提供。缘由是具体类型的Metal对象须要依赖其具体的机器型号。这也是为了适应接口编程而不是实现编程。这也意味着你不用在继承Metal类或者添加扩展以及去使用runtime的风险了。(做者很不自信啊。。。)优化
Metal为了速度某种程度上牺牲了安全性。举个栗子,你收到了一个指向内部缓存的空指针,这时候你的操做须要额外当心。当OpenGL这类场景出错的时候一般是黑屏。Metal的话多是随机错误,跳屏或者崩溃都是妥妥的。动画
坑爹的是模拟器不支持。。。(这句话早说出来会被打吧,还有就是我是4S机器不支持硬件都不支持。。)ui
首先能够到Github下个DEMO。
在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];
CAMetalLayer
是CALayer
的一个子类用来展现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];
大概如此。。!
基础是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一直都为iOS提供对应的扩展框架。可是从内部改变OpenGL彷佛应该是一个困难的任务由于它须要兼容多平台的缘由(不可能定制)。尽管OpenGL一直在前进,可是进度很慢而且效果微乎其微。
另外一方面Metal是大苹果平台专属工做。尽管API使用协议看上去怪怪得,可是它用起来仍是蛮不错的。Metal使用OC写的,以Fundation为基础,并使用了GCD来同步CPU和GPU。对比OpenGL它更高度抽象了GPU的流水线能够作到不用彻底的重写。
应该尚未整好。。。(略)
总结了现状是不少人还用不上Metal,可是高级码农已经开始使用而且从中获益。 若是你想彻底发挥硬件的潜力,Metal可让开发者在游戏中创造出独一无二效果,或是更加快速的并行计算,让他们(产品)更具备竞争力。