设备和命令 <- Metal

展示如何访问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的命令对象之间的关系。作为总结:

  1. 命令缓存器(Command buffers)创建于命令队列(Command queue)。
  2. 命令编码器(Command encoders)把命令编码到命令缓存器。
  3. 之后命令缓存器会被提交并发送给GPU。
  4. GPU执行这些命令,并用结果渲染drawable。
设备和命令 <- Metal_第1张图片

准备一个帧

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渲染基本几何图形。

源码下载

你可能感兴趣的:(设备和命令 <- Metal)