Hello Metal
本文为了了解 Metal 以及实现一个简单的颜色变化案例
实现效果如下:
本案例分为两大类,自定义渲染循环和控制器的调用
自定义渲染循环
在我们开发 Metal 程序时,将渲染循环分为自己创建的类,是非常有用的一种方式,使用单独的类,我们可以更好管理初始化 Metal 以及 Metal 视图委托。自定义渲染循环主要包括三大模块
初始化
需要传入一个 MTKView 对象,通过传入的 view,获取 Metal 设备以及创建命令队列
- 设置 device
_device = mtkView.device;
此处的 device 并不是新建的,是由传入 view 在外面创建好的,可以通过 view 获取
- 设置命令队列
_commanQueue = [_device newCommandQueue];
所有应用程序需要与 GPU 交互的第一个对象是 MTLCommandQueue 对象。使用 MTLCommandQueue 去创建对象,并且加入 MTLCommandBuffer 对象中,为当前渲染的每个渲染传递创建一个新的命令缓冲区
Metal 命令对象之间的关系
- 命令缓冲区(command buffer)是从命令队列中(command queue)创建的
- 命令编码器(command encoders)将命令编码到命令缓冲区中
- 提交命令缓冲区并将其发送到 GPU
- GPU 执行命令并将结果呈现为可绘制
设置颜色
- (Color)makeFancyColor;
随着帧率变化的颜色,详细可见文末完整代码链接
实现代理协议
MTKViewDelegate 需要实现两个代理方法
// 每当视图需要渲染时调用
- (void)drawInMTKView:(nonnull MTKView *)view;
根据视图(view)属性上设置帧速率,每当到指定时间时,就会触发 view 的渲染,继而回调 drawInMTKView: 代理方法进行绘制渲染。
- 获取颜色值
Color color = [self makeFancyColor];
通过 makeFancyColor 函数获取当前帧显示的颜色
- 设置 view 的清屏颜色
view.clearColor = MTLClearColorMake(color.red, color.green, color.blue, color.alpha);
类似 OpenGL ES 中的 glClearColor
- 创建命令缓冲区
id<MTLCommandBuffer> commandBuffer = [_commanQueue commandBuffer];
commandBuffer.label = @"MyCommand";
使用 MTLCommandQueue 创建对象并且加入到 MTCommandBuffer 对象中去,为当前渲染的每个渲染传递创建一个新的命令缓冲区
- 获得渲染描述符
MTLRenderPassDescriptor *renderPassDescriptor = view.currentRenderPassDescriptor;
从视图(view)属性中获取,用于在 commandBuffer 中创建 MTLRenderCommandEncoder 对象
- 创建 MTLRenderCommandEncoder 对象
id<MTLRenderCommandEncoder> renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
renderEncoder.label = @"MyRenderEncoder";
通过渲染描述符 renderPassDescriptor 创建 MTLRenderCommandEncoder 对象,即命令渲染编辑器,相当于 OpenGL ES 中的 program,主要用途是用于绘制对象
- 结束渲染编辑
[renderEncoder endEncoding];
任务已经完成,不需要绘制,结束 MTLRenderCommandEncoder 工作
- 添加一个最后的命令来显示清除的可绘制的屏幕
[commandBuffer presentDrawable:view.currentDrawable];
当编码器结束之后,命令缓存区就会接受到2个命令:
1) present;
2) commit。
因为 GPU 是不会直接绘制到屏幕上,因此你不给出去指令。是不会有任何内容渲染到屏幕上
- 完成渲染并将命令缓冲区提交给GPU
[commandBuffer commit];
相当于 OpenGL ES 中的 调用 draw
- (void)mtkView:(nonnull MTKView *)view drawableSizeWillChange:(CGSize)size;
当 MTKView 视图发生大小改变时调用,这个案例没用到,就不多解释了,后面会跟进
控制器调用
主要是加载 view 以及 view 传递给 render 渲染循环类
创建 MTKView 对象
_view = [[MTKView alloc] initWithFrame:self.view.bounds];
[self.view addSubview:_view];
MTKView 继承自 UIView,可以调用 initWithFrame: 方法
为 MTKView 对象设置 device
_view.device = MTLCreateSystemDefaultDevice();
一个 MTLDevice 对象就代表这着一个 GPU,通常我们可以调用方法MTLCreateSystemDefaultDevice() 来获取代表默认的 GPU 单个对象
创建 LcRender
_render = [[LcRender alloc] initWithMetalKitView:_view];
在我们开发 Metal 程序时,将渲染循环分为自己创建的类,是非常有用的一种方式,使用单独的类,我们可以更好管理初始化 Metal 以及 Metal 视图委托
设置代理
_view.delegate = _render;
由 LcRender 来实现 MTKView 的代理方法
设置帧速率
_view.preferredFramesPerSecond = 60;
视图可以根据视图属性上设置帧速率,即指定时间来调用 drawInMTKView 方法--视图需要渲染时调用
完整代码见GitHub Hello Metal