Metal简述
Metal是2018年苹果推出的用于取代在苹果端的业务的图形编程接口,在2018年之前使用的是基于OpenGL ES 封装的GLKit,通过Metal相关API直接操作GPU,能最大限度的利用GPU能力。因此使用Metal必须使用iPhone6s及以上机型且必须是真机(模拟器不支持GPU,模拟器使用的是CPU来模拟GPU)。
特点
- CPU低消耗性
- 更高效的GPU性能,Metal能更好的发挥GPU的性能
- 提高CPU与GPU的并发性
- 有效的资源管理
如下图介绍
图形管道
Metal的图形管道通OpenGL大体相同,如图所示
- CPU将顶点数据传到顶点着色器
- 顶点着色器将处理好的顶点进行图元装配
- 进行光栅化处理
- 将光栅化的数据传给片元着色器处理
- 渲染到屏幕显示
Metal 命令与对象之间的关系
-
命令缓存区(command buffer):从命令队列(Command Queue)中创建
-
命令编码(Command encoder):将命令编码到命令缓冲区中
-
提交命令缓冲区并将其发送到GPU
-
GPU执行命令并将结果呈现为可绘制
Metal 部分API简介
MTKView
在MetalKit中提供了一个视图类MTKView,类似于GLKit中GLKView,它是NSView(macOS中的视图类)或者UIView(iOS、tvOS中的视图类)的子类。用于处理metal绘制并显示到屏幕过程中的细节。因此使用的时候一种是将self.View强转成MTKView,当然self.View的类型需要是MTKView的类型,否则会崩溃;另一种就是直接创建MTKView。
MTLDevice
在Metal中操作的是GPU,因此必须达到GPU对象才可以操作。Metal提供了MTLDevice协议接口来操作GPU,创建MTLDevice,如下
view.device = ;MTLCreateSystemDefaultDevice();
其中view是MTKView类型
MTLCommandQueue
命令队列,与GPU交互的第一个对象,其保存了将要渲染的命令对象
MTLCommandBuffer
命令缓冲区,用来存储命令
MTLRenderCommandEncoder
表示单个渲染过程中相关联的渲染状态和渲染命令,有以下功能
-
指定图形资源:例如缓存区和纹理对象,其中包含顶点、片元、纹理图片数据
-
指定一个
MTLRenderPipelineState对象,表示编译的渲染状态,包含顶点着色器和片元着色器的编译&链接情况 -
指定固定功能,包括视口、三角形填充模式、剪刀矩形、深度、模板测试以及其他值
-
绘制3D图元
Apple 建议
-
Separate Your Rendering Loop:分开渲染循环,苹果希望渲染放在一个单独的类中处理
-
Respond to View Events:即MTKDelegate,响应视图事件也要放在单独的类中处理
-
Metal Command Objects:创建一个命令对象,即创建执行命令的GPU、与GPU交互的
MTLCommandQueue对象以及MTCommandBuffer渲染缓存区
案例分析
颜色渲染案例
-
创建MTKView
self.mtkView = (MTKView *)self.view; -
设置device
self.mtkView.device = MTLCreateSystemDefaultDevice(); -
设置metal代理
self.renderView = [[SFxRender alloc] initWithMetalView:self.mtkView]; if (!self.renderView) { NSLog(@"render 失败"); return; } self.mtkView.delegate = self.renderView; self.mtkView.preferredFramesPerSecond = 60;
渲染分类
-
根据device创建命令队列
-(instancetype)initWithMetalView:(MTKView *)mtkView { self = [super init]; if (self) { self.device = mtkView.device; self.commandQueue = [self.device newCommandQueue]; } return self; } -
使用命名队列创建命令缓冲区
//创建命令缓冲区 id<MTLCommandBuffer> commandBuffer = [self.commandQueue commandBuffer]; commandBuffer.label = @"myBuffer"; -
获得渲染描述符
//从视图绘制中获得渲染描述符 MTLRenderPassDescriptor *passDescriptor = view.currentRenderPassDescriptor; -
创建命令编码器
//创建命令编码器 id<MTLCommandEncoder> commanderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:passDescriptor]; commanderEncoder.label = @"myEncoder"; //本demo只是变换颜色,所以创建命令编码器即可 //停止编码 [commanderEncoder endEncoding]; -
显示清除的可绘制屏幕
/* 当编码器结束之后,命令缓存区就会接受到2个命令. 1) present 2) commit 因为GPU是不会直接绘制到屏幕上,因此你不给出去指令.是不会有任何内容渲染到屏幕上. */ //添加一个最后的命令来显示清除的可绘制的屏幕 [commandBuffer presentDrawable:view.currentDrawable]; -
提交绘制
//提交绘制 [commandBuffer commit]; -
动态色值计算
-(Colors)getRandomColor { static BOOL growing = YES; //设置颜色通道的值[0, 1] static NSUInteger primaryChannel = 0; //颜色通道数组 static float colorsChannel[] = {1.0, 0.0, 0.0, 1.0}; //颜色变化的步长 const float dynamicColorRate = 0.015; if (growing) { //获取动态信道索引 (0,1,2,3)之间变换 NSUInteger dynamicChannelIndex = (primaryChannel + 1) % 3; //对应色值自增 colorsChannel[dynamicChannelIndex] += dynamicColorRate; if (colorsChannel[dynamicChannelIndex] >= 1.0) {//最大是1.0 growing = NO; //将颜色通道改为动态颜色通道 primaryChannel = dynamicChannelIndex; } }else{ NSUInteger dynamicChannelIndex = (primaryChannel + 2) % 3; colorsChannel[dynamicChannelIndex] -= dynamicColorRate; if (colorsChannel[dynamicChannelIndex] <= 0.0) { growing = YES; } } Colors color; color.red = colorsChannel[0]; color.green = colorsChannel[1]; color.blue = colorsChannel[2]; color.alpha = colorsChannel[3]; return color; }