创建一个 MetalKit View 并使用 render pass 来绘制视图的内容。
概述
作为 Metal 初见系列的第二篇,我们主要探索使用 Metal 渲染图形内容的基础知识。使用 MetalKit 框架创建一个视图,该视图的内容使用 Metal 绘制。我们将通过 render pass 过程中的 encode commands 将视图擦除为指定的背景颜色。
准备一个 MetalKit View 来绘制
MetalKit 提供了一个名为 MTKView 的类,它是 NSView (在macOS中) 或 UIView (在 iOS 和 tvOS 中) 的子类。MTKView 内部实现了使用 Metal 绘制的内容显示到屏幕上相关的诸多细节。
MTKView 创建内部资源需要使用 MetalDevice 对象,因此我们第一步是将视图的 device 属性设置为现有的 MTKView。
_view.device = MTLCreateSystemDefaultDevice();
MTKView 提供了一些属性让我们来控制他的行为。想要将视图背景擦除到指定的颜色。我们可以使用 MTLClearColorMake(:::🙂 函数,需要指定红、绿、蓝、和 α 值。
_view.clearColor = MTLClearColorMake(0.0, 0.5, 1.0, 1.0);
因为本次我们没有渲染没有动画,我们只需要配置视图在需要更新的时候更新。例如:视图形状改变。
_view.enableSetNeedsDisplay = YES;
委托代理职责
MTKView 的渲染内容由我们的 App 提供。MTKView 渲染时使用代理模式通知我们的 App,为了保证收到代理的回调,我们需要给 MTKView 的 delegate 属性设置一个对象,实现 MTKDelegate 协议。
_view.delegate = _renderer;
代理需要实现两个方法:
- 当视图大小发生变化时或当设备方向变化 (iOS),我们的应用程序会适应分辨率显示视图的大小。此时调用 mtkView (_: drawableSizeWillChange) 方法,仅当内容的大小发生变化时。
- 当需要更新视图的内容时,视图调用 draw(in:) 方法。在这个方法中,我们将创建一个命令缓冲区,通过 command buffer 告诉 GPU 我们要在屏幕上显示什么。这个有时候也叫渲染一帧。我们可以认为一帧,所有的工作都是为了产生一个可以显示在屏幕上图像。在可交互式应用程序中,比如游戏,我们可能会需要很多帧。
在本示例中,我们创建一个名为 AAPLRenderer 类,实现委托方法和绘图的责任。视图控制器会创建这个类的一个实例,并设置它作为视图的委托。
创建一个 Render Pass Descriptor
绘制时,GPU 将结果存储到纹理中,纹理是包含图像数据且可供 GPU 访问的内存块。在本示例中,MTKView 创建了我们需要绘制到视图中的所有纹理。我们可以创建多个纹理,在显示一个纹理的时候渲染到另一个纹理以供显示。
要进行绘制,我们需要创建一个 render pass,它是绘制到一组纹理中的一系列渲染命令。在 render pass 中,纹理也称为渲染目标。要创建 render pass,我们需要创建一个
MTLRenderPassDescriptor 对象,在此示例中,我们不需要配置自己的渲染 MTLRenderPassDescriptor,MetalKit 视图会帮我们创建一个。
MTLRenderPassDescriptor *renderPassDescriptor = view.currentRenderPassDescriptor;
if (renderPassDescriptor == nil)
{
return;
}
RenderPassDescriptor 描述渲染目标集合,以及在渲染过程开始和结束时应如何处理它们。RenderPass 还定义了渲染的其他一些方面,这些方面不属于本示例。视图不仅会返回带有指向视图纹理之一的单一颜色附件的 RenderPassDescriptor,还会根据视图的属性配置 RenderPass。这意味着默认情况下,在渲染开始时,渲染目标被擦除为与视图属性匹配的纯色,并且在渲染通道结束时,所有更改都存储到纹理。
注意视图 RenderPassDescriptor 可能是 nil ,调用之前需要做判空处理。
创建一个 Render Pass
我们可以使用 MTLRenderCommandEncoder 对象创建 RenderPassDescriptor 。调用 Command Buffer 的 MTLRenderCommandEncodermakeRenderCommandEncoder(descriptor:) 方法,传入 RenderPassDescriptor 对象。
id<MTLRenderCommandEncoder> commandEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
在本示例中,我们不需要对任何绘图命令进行编码,因此 RenderPass 所做的唯一事情就是擦除纹理。调用编码器的 endEncoding
方法以指示传递完成。
[commandEncoder endEncoding];
将绘制对象显示在屏幕上
事实上不是所有的纹理都能展示,一个渲染好的纹理也不能自动展示在屏幕上。在 Metal 中,能被显示在屏幕上的纹理都被 drawable objects,对象管理,包括显式内容展现。
MTKView 自动创建 drawable objects 来管理纹理,通过 currentDrawable 属性可以获取到可绘制的 drawable 对象,包含当前 RenderPass 的目标纹理。视图会返回一个关联 Core Animation 的 CAMetalDrawable 对象。
id<MTLDrawable> drawable = view.currentDrawable;
调用 command buffer 的 present(_:) 方法,传入 drawable 对象。
[commandBuffer presentDrawable:drawable];
这个方法告诉 Metal,当命令缓冲区被执行时,Metal 应该配合 Core Animation 在渲染完成后显示纹理。当 Core Animation 呈现纹理时,它成为视图的新内容。在本示例中,这意味着擦除的纹理成为视图的新背景。这一变化与 Core Animation 为屏幕上的用户界面元素所做的任何其他视觉一起更新。
提交到命令缓冲区
到此处我们已经完成了当前帧的全部工作,最后我们要提交 command buffer。
[commandBuffer commit];
Demo 示例
UsingMetalToDrawAViewContentsents.zip