展示如何访问GPU并与之交互。
概览
在本例中,你将学到如何使用Metal来写一个应用,并将将基本渲染命令发布到GPU上。尤其是,你将学习如何或许Metal设备、配置MetalKit视图、创建并执行GPU命令、以及显示渲染内容。
Metal和Metal框架
本例使用两个框架来显示渲染内容:Metal和MetalKit。Metal提供访问GPU的入口,而MetalKit提供可以使开发Metal应用更容易的命令工具。它们一起无缝地整合操作系统和其他框架,让你可以聚焦在GPU编程。
MetalKit中最常用的是MTKView类,它封装了UIView或NSView对象,并配置特定于Metal的核心动画(Core Animation)功能。尤其是,一个MetalKit视图自动设置并管理持续渲染循环,它为每个帧(frame)提供通常被称为drawable的2D显示资源。
注意
你可以使用Core Animation直接开发Metal应用,但是实用MetalKit会更容易、快速、且方便。
分解你的渲染循环
当开发Metal应用时,把你的渲染循环分解到它自己的类中是非常有用的。实用分解的类,你可以更好的管理你的初始Metal设置代码及每一帧的Metal命令。这种公共架构是通过AAPLRenderer类描述的,该类在MetalKit视图中被初始化,并作为视图的委托被指派。
_renderer = [[AAPLRenderer alloc] initWithMetalKitView: _view];
if(!_ renderer)
{
NSLog(@"Renderer failed initialization");
return;
}
_view.delegate = _renderer;
相应视图事件
MTKViewDelegate对象实现 mtkView:drawableSizeWillChange:和drawInMTKView:方法。这些方法通知你的MetalKit视图的尺寸及绘制事件的渲染器。
- 当窗口尺寸(macOS)或者布局操作发生改变(例如iOS或tvOS的设备方向改变)发生的时候,视图调用mtkView:drawableSizeWillChange:方法。如有必要,你可以响应视图的新尺寸并改变你的渲染分辨率。
- 当视图渲染一个新的帧(例如通过设置视图的preferredFramesPerSecond属性来址 sing帧率,例如60FPS)时,视图调用drawInMTKView:方法。该回调通常是开始执行渲染循环的主要事件。
Metal命令对象
MTLDevice对象代表的是GPU。通常,你调用MTLCreateSystemDefaultDevice()方法来获取一个MTLDevice单例对象,它代表一个设备的默认GPU。在多GPU的macOS设备上,调用MTLCopyAllDevices()方法返回一个数组,其元素是设备的所有GPU。MTLDevice对象提供关于GPU的信息,但是它的主要目的是创建其他可以与GPU交互的对象。
第一个需要与GPU交互的对象是MTLCommandQueue。
_commandQueue = [_device newCommandQueue];
你使用MTLCommandQueue对象来创建并管理MTLCommandBuffer对象,确保它们以正确的顺序被发送到GPU。
对于每一帧来说,一个新的MTLCommandBuffer对象充满了需要被GPU执行的命令。
id commandBuffer = [_commandQueue commandBuffer];
有很多不同类型的GPU,每个GPU接受并以自己独一无二的方式解释命令。 MTLCommandBuffer把这些命令合并在一个单独的提交(submission),但是必须首先使用MTLCommandEncoder对象把它们被编码为与设备无关。有几种不同类型的MTLCommandEncoder类,每一种都用于执行不同类型的GPU任务。本例展示的是MTLRenderCommandEncoder子类的使用,它把渲染命令编码如命令缓存器。
本例使用MTLRenderCommandEncoder对象来编码为MetalKit视图的drawable渲染像素的命令。为此,渲染命令编码器必须与该drawable特定相关。
为了创建MTLRenderCommandEncoder对象,你必须首先创建MTLRenderPassDescriptor对象。MTLRenderPassDescriptor对象是一个轻量级的临时对象,它有很多可以被现有的MTLCommandBuffer对象配置的属性,这些属性被用于创建新的MTLRenderCommandEncoder对象。之后,该MTLRenderPassDescriptor对象就不再需要了。
下面的图表说明了Metal的命令对象之间的关系。作为总结:
- 命令缓存器(Command buffers)创建于命令队列(Command queue)。
- 命令编码器(Command encoders)把命令编码到命令缓存器。
- 之后命令缓存器会被提交并发送给GPU。
- GPU执行这些命令,并用结果渲染drawable。
准备一个帧
MetalKit视图通过currentRenderPassDescriptor属性为每个帧创建一个新的MTLRenderPassDescriptor对象。该渲染遍历表述器(render pass descriptor)预设了视图特定的属性,有些是视图的drawable派生的,可以被用来容易且方便的创建新的MTLRenderCommandEncoder对象。
// 获取一个产生于视图的drawable纹理的renderPassDescriptor
MTLRenderPassDescriptor *renderPassDescriptor = view.currentRenderPassDescriptor;
// 如果我们已经有了renderPassDescriptor,我们可以渲染drawable;否则我们将跳过该帧的任何渲染。
if(renderPassDescriptor != nil)
{
id renderEncoder =
[commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
// 代码未完......
}
被编码进MTLRenderCommandEncoder对象的命令渲染视图的drawable。默认情况下,隐式的创建一个MTLRenderCommandEncoder对象,编码一个在其他渲染命令之前被GPU执行的清除命令。该清除命令将drawable的像素在开始渲染循环时设置为透明色。
view.clearColor = MTLClearColorMake(color.red, color.green, color.blue, color.alpha);
完成一个帧
通常,Metal应用调用多个MTLRenderCommandEncoder方法将明确的渲染命令编码到命令缓存器中。为了简单起见,本例没有编码明确的渲染命令;只是隐式的清除命令被编码。在创建了MTLRenderCommandEncoder对象之后,本例简单的调用了endEncoding方法来表明该编码器完成。
当编码器完成的时候,命令缓存器接受两个最终命令:呈现(present)和提交(commit)。
因为GPU不直接绘制屏幕,在它完成执行命令之前它被阻止绘制像素。为了避免不完整的drawable导致的糟糕用户体验,你应该调用presentDrawable:方法。该方法告诉Metal在呈现到屏幕之前GPU一定要完成渲染。
[commandBuffer presentDrawable:view.currentDrawable];
GPU也不能立刻执行命令。调用MTLRenderCommandEncoder或MTLCommandBuffer对象只有在提交(commit)方法被调用之后才会执行。然后Metal安排命令缓存器执行。当GPU开始执行,drawable会被新的颜色清除。当GPU执行完成,被渲染的drawable被呈现在屏幕上。
[commandBuffer commit];
下一步
在本例中,你学习了如何使用Metal编写应用,并向GPU发送基本的渲染命令。
在Hello Triangle例子中,你将学到如何用Metal渲染基本几何图形。
源码下载