1. 认识Metal
1.1 定义
先贴一段苹果的官方说明。
Metal provides near-direct access to the graphics processing unit (GPU), enabling you to maximize the graphics and compute potential of your apps on iOS, macOS, and tvOS. Building on an approachable, low-overhead architecture with precompiled GPU shaders, fine-grained resource control, and multithreading support, Metal further evolves support for GPU-driven command creation, simplifies working with the array of Metal-capable GPUs, and lets you tap into Pro power of Mac Pro and Pro Display XDR.
大概意思就是说Metal很牛叉吧。提供了近乎可以直接访问系统GPU的能力。在图像渲染,通用计算方面,通过发挥 GPU 的特性,极大提升性能。
1.2 metal的图形管道
metal的图像显示流程如下图所示:
- CPU将顶点数据传到GPU
- 顶点着色器处理顶点数据
- 处理好的顶点数据进行图元装配
- 光栅化,得到每个像素点的颜色值
- 片元着色器返回最终要显示的颜色
- 颜色数据存入帧缓冲区,最终显示到屏幕上
1.3 Metal使用建议
1.3.1 Separate Your Rendering Loop
在我们开发Metal 程序时, 将渲染循环分为自己创建的类, 是非常有用的一种方式, 使用单独的类, 我们可以更好管理初始化Metal, 以及Metal视图委托.
//创建一个单独的Render类来处理渲染方面的逻辑 与其他的视图操作分开
render = Render.init(mtkView: view)
//进行一下必要的初始化操作
render?.mtkView(view, drawableSizeWillChange: view.drawableSize)
//代理设置为render
view.delegate = render
1.3.2 Respond to View Events
//render来实现MTKViewDelegate的代理方法
func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize)
func draw(in view: MTKView)
1.3.3 Metal Command Objects
创建一个MTLCommandQueue对象,通过它来处理与GPU之间的交互。
//Metal与GPU 交互的第一个对象
commandQueue = device!.makeCommandQueue()
1.4 Metal对象之间的关系
- 命令缓存区(command buffer) 是从命令队列(command queue) 创建的
- 命令编码器(command encoders) 将命令编码到命令缓存区中
- 提交命令缓存区并将其发送到GPU
- GPU执⾏命令并将结果呈现为可绘制.
2 Metal 小案例
案例效果图如下:
2.1 创建MTKView
在MetalKit中提供了一个视图类MTKView,类似于GLKit中GLKView,用于处理Metal绘制并显示到屏幕过程中的细节。
我们直接将storyboard中的view的类型改为MTKView。使得在控制器带初始化的时候,自动为我们创建一个MTKView,供我们使用。
获取
MTKView:
//1. 获取MTKView
let view = self.view as! MTKView
2.2 为MTKView 设置MTLDevice
一个MTLDevice对象就代表这着一个GPU, 通常我们可以调用方法MTLCreateSystemDefaultDevice() 来获取代表默认的GPU单个对象。并且在创建完成后,需要判断是否获取GPU的使用权限,如果不成功,则中断渲染流程。
//2.为MTKView 设置MTLDevice(必须)
view.device = MTLCreateSystemDefaultDevice()
//3.判断是否设置成功
if view.device == nil {
print("Metal is not supported on this device")
return
}
2.3 创建Render
我们听从苹果大大的建议,创建一个单独处理Metal的类——Render。通过自定义构造方法,保存mtkView的device,并根据device创建commandQueue,同样将commandQueue保存起来以供后续使用。
//声明变量
var device: MTLDevice?
var commandQueue: MTLCommandQueue?
//自定义构造方法
convenience init(mtkView: MTKView) {
self.init()
device = mtkView.device
//Metal与GPU 交互的第一个对象
commandQueue = device!.makeCommandQueue()
}
在viewcontroller的viewDidLoad方法中创建render对象,并将render设置为mtkView的代理。
//4. 创建Render
render = Render.init(mtkView: view)
//5.判断render 是否创建成功
if render == nil {
print("Renderer failed initialization")
return
}
//6.设置MTKView 的代理(由CCRender来实现MTKView 的代理方法)
view.delegate = render
2.4 在render实现代理方法
MTKViewDelegate协议包含下面两个方法。
//当MTKView视图发生大小改变时调用
func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize)
//每当视图需要渲染时调用
func draw(in view: MTKView)
根据设置好的帧率,每到特定的时间点,系统会自动调用draw方法来进行渲染。因此,我们的实现代码也写在draw方法里面。
这个小案例没有什么复杂的渲染逻辑,是简单的设置一下清屏颜色就好了。首先通过自定的方法获取一个随机颜色,然后为mtkView的clearColor属性赋值就OK了。
//1. 获取颜色值
let color = makeFancyColor()
//2. 设置view的clearColor
view.clearColor = MTLClearColorMake(color.red, color.green, color.blue, color.alpha)
注意: 以下操作步骤为固定流程,以后都要用到。
为当前渲染的每个渲染传递创建一个新的命令缓冲区。使用MTLCommandQueue创建对象并且加入到MTCommandBuffer对象中去。
//3.为当前渲染的每个渲染传递创建一个新的命令缓冲区
let commandBuffer = commandQueue?.makeCommandBuffer()
commandBuffer?.label = "MyCommand"
从视图绘制中,获得渲染描述符,类型为MTLRenderPassDescriptor。在创建MTLRenderCommandEncoder会用到
let renderPassDescriptor = view.currentRenderPassDescriptor
通过渲染描述符renderPassDescriptor创建MTLRenderCommandEncoder 对象,即命令渲染编辑器。我们可以使用MTLRenderCommandEncoder 来绘制对象,但是这个demo我们仅仅创建编码器就可以了,我们并没有让Metal去执行我们绘制的东西,这个时候表示我们的任务已经完成。
let renderEncoder = commandBuffer?.makeRenderCommandEncoder(descriptor: renderPassDescriptor )
renderEncoder?.label = "renderEncoder"
当编码器结束之后,命令缓存区就会接受到2个命令。
1) present
2) commit
因为GPU是不会直接绘制到屏幕上,因此你不给出去指令。是不会有任何内容渲染到屏幕上的。所以,我们最后要发出这两条指令。
//8.添加一个最后的命令来显示清除的可绘制的屏幕
commandBuffer?.present(view.currentDrawable!)
//9.在这里完成渲染并将命令缓冲区提交给GPU
commandBuffer?.commit()
最后附上完整demo。