[MetalKit]Introducing Metal 2介绍Metal2

本系列文章是对 http://metalkit.org 上面MetalKit内容的全面翻译和学习.

MetalKit系统文章目录


今年的WWDC可能是有史以来最重要的,至少目前我们 - Metal开发者 - 终于被关注了.我可以诚心诚意地讲这是我生命中最棒的一周!

让我们看看Games and Graphics游戏和图形新闻.最意想不到的是,将Metal重命名为Metal 2.自从它在2014年第一次被发布后,它终于有了最显著的增加和增强,确实,但我们必须承认:没人预见到这一变化的来临.最意想不到的奖品是新的ARKit框架.在keynote演示之后仅仅几周,就已经有了大量大胆有趣的增强现实项目放出来. ARKit融入Metal非常容易.最后,最有影响力的奖品是VR.这是因为虚拟现实现在能够实现更低延迟,更高帧率,还有更强劲的内置显卡,现在也有了外置显卡external GPUs.

vr.png

Model I/O, SpriteKitSceneKit框架也都添加了新特性.其它令人感兴趣的增加是用于机器学习 machine learning的CoreMLVision框架.本文只关注Metal中的新特色:

1). MPS - Metal Performance Shaders目前在macOS上也可以使用了,添加的新特性包括:

  • 四种新的图片处理基本体(Image Keypoints图像关键点, Bilinear Rescale双线性重缩放, Image Statistics图像统计, Element-wise Arithmetic Operations元素运算符).
  • 新的线性代数对象如MPSVector, MPSMatrixMPSTemporaryMatrix,还有*BLAS-style matrix-matrix and matrix-vector multiplication - BLAS式的矩阵-矩阵及矩阵-向量乘法,LAPACK-style triangular matrix factorization and linear solvers - LAPACK式三角矩阵分解与线性求解器`.
  • 一系列新的CNN基本体.
  • Binary二进制卷积, XNOR同或卷积, Dilated空洞卷积, Sub-pixel子像素卷积Transpose转置卷积的卷积层被添加到现有的Standard标准卷积基本体内.
  • 添加了一个新的Neural Network Graph神经网络图形API,在用过滤器和图形节点来描述神经网络时非常有用.
  • 现在也有了Recurrent Neural Networks循环神经网络,它可以改善CNNs一对一的局限性,实现一对多和多对多的关系.

2). Argument Buffers参数缓冲器/变元缓冲器 - 可能是今年对框架最重要的添加.在传统的增强模型中,我们会为每个对象调用许多函数来设置缓冲器,纹理,线性采样,最后为该对象调用绘制命令.

[MetalKit]Introducing Metal 2介绍Metal2_第1张图片
ArgumentBuffers1.png

正如你想象的那样,,当将该数量乘以物体总数量及绘制帧数时,调用数急剧增长.最终这会限制屏幕上出现的物体数量.


[MetalKit]Introducing Metal 2介绍Metal2_第2张图片
ArgumentBuffers2.png

Argument Buffers参数缓冲器引入了一个高效的新途径来使用资源,通过采用始终存在的indirect behavior间接行为,来将其应用到纹理,采样,状态,指向其它缓冲器的指针,等等.参数缓冲器将只有每物体2个API调用:设置参数缓冲器,然后绘制.这样可以绘制更多物体.

[MetalKit]Introducing Metal 2介绍Metal2_第3张图片
ArgumentBuffers3.png

参数缓冲器很容易使用,就像匹配着色器数据和主机数据一样:

struct Material {
    float intensity;
    texture2d aTexture;
    sampler aSampler;
}

kernel void compute(constant Material &material [[ buffer(0) ]]) {
    ...
}

CPU上,参数缓冲器是被MTLArgumentEncoder对象创建和使用的,然后它可以轻易地在CPUGPU之间做位块传送:

let function = library.makeFunction(name: "compute")
let encoder = function.makeIndirectArgumentEncoder(bufferIndex: 0)
encoder.setTexture(myTexture, index: 0)
encoder.constantData(at: 1).storeBytes(of: myPosition, as: float4)

但是使用dynamic indexing动态索引特性的话还可以更好.举例,当渲染拥挤时,参数缓冲器数组可以将所有实例(物体)的数据打包到一起.然后,不再需要每个物体调用两次,改为每帧2个API调用:一个设置到缓冲器,一个为大量实例绘制索引的基本体!

[MetalKit]Introducing Metal 2介绍Metal2_第4张图片
ArgumentBuffers4.png

另一个应用参数缓冲器的例子是,当运行粒子模拟时.对此,我们有resource setting on the GPU特性,它的意思是有一个参数缓冲器数组,每个粒子(线程)一个缓冲器.所有的粒子特性(位置,材料,等)在GPU上的参数缓冲器中被创建和储存的,这样当一个粒子需要某个属性时,比如材料,它将从参数缓冲器中复制出来,而不再需要从CPU获取,这样可以避免昂贵的CPUGPU之间的复制开销.

[MetalKit]Introducing Metal 2介绍Metal2_第5张图片
ArgumentBuffers5.png

复制内核很简单,可以让你赋值一个常数,从源对象部分复制或者完全复制到目标对象:

kernel void reuse(constant Material &source [[ buffer(0) ]],
                  device Material &destination [[ buffer(1) ]]) {
    destination.intensity = 0.5f;
    destination.aTexture = source.aTexture;
    destination = source;
}

最后,还有一个应用例子是引用其它参数缓冲器(multiple indirections).想象有一个instance结构体表示一个指向Material材料结构体的实例(特征),这样的话就会有许多实例指向同一个材料.同样的,想象另一个表示一个树状节点的结构体,其中每个节点将会有一个指针指向Instance实例结构体,起到节点中的一个实例数组的作用:

struct Instance {
    float4 position;
    device Material *material;
}

struct Node {
    device Instance *instances;
}

注意:目前为止,只有Tier 2设置支持所有的参数缓冲器特性.从Metal 2开始,GPU设备分成了Tier 1(整体式)和Tier 2(分离式).

3). Raster Order Groups光栅扫描顺序组 - 一个新的片段着色器同步基本体,它在片段着色器访问内存时允许对顺序进行更精细的控制.例如,当使用自定义混合时,大部分图形APIs保证混合是按绘制调用顺序发生的.然而,GPU的并行线程需要一个方法来防止竞争条件.Raster Order Groups光栅扫描顺序组通过提供给我们一个隐含的Wait命令来保证.

[MetalKit]Introducing Metal 2介绍Metal2_第6张图片
RasterOrderGroups.png

在传统混合模式下,要创建竞争条件:

fragment void blend(texture2d out[[ texture(0) ]]) {
    float4 newColor = 0.5f;
    // non-atomic memory access without any synchronization
    float4 oldColor = out.read(position);
    float4 blended = someCustomBlendingFunction(newColor, oldColor);
    out.write(blended, position);
}

需要做的就是添加Raster Order Groups属性到纹理(或资源)中来解决访问冲突:

fragment void blend(texture2d 
                out[[texture(0), raster_order_group(0)]]) {
    float4 newColor = 0.5f;
    // the GPU now waits on first access to raster ordered memory
    float4 oldColor = out.read(position);
    float4 blended = someCustomBlendingFunction(newColor, oldColor);
    out.write(blended, position);
}

4). ProMotion自适应刷新率 - 目前只在iPad Pro显示屏上可用.没有ProMotion时典型的帧率是60FPS(16.6ms/frame):

[MetalKit]Introducing Metal 2介绍Metal2_第7张图片
promotion1.png

使用ProMotion后帧率提升到120FPS(8.3ms/frame),这对于用户输入非常有用,如手指触摸或pencil使用:

[MetalKit]Introducing Metal 2介绍Metal2_第8张图片
promotion2.png

ProMotion也提供了弹性机制来刷新显示图片,所以我们不需要使用固定帧率.不使用ProMotion会有图片刷新不一致,这对用户体验很不好.开发者为了实现一致性,通常会将峰值帧率强制保持在30FPS,而不是理想的48FPS(20.83ms/frame):

[MetalKit]Introducing Metal 2介绍Metal2_第9张图片
promotion3.png

使用ProMotion我现在可以每4ms就有一个刷新点,而不再是每16ms(竖直的白线):

[MetalKit]Introducing Metal 2介绍Metal2_第10张图片
promotion4.png

ProMotion同样有助于掉帧.不使用ProMotion时,可能一帧画面花了太长时间来显示,就错过了截止期限:

[MetalKit]Introducing Metal 2介绍Metal2_第11张图片
promotion5.png

ProMotion通过对帧扩展4ms而不是一个完整帧(16.6ms)来解决这个问题:

[MetalKit]Introducing Metal 2介绍Metal2_第12张图片
promotion6.png

UIKit动画自动使用ProMotion,但是在Metal视图中使用ProMotion,你需要在项目的Info.plist文件中设置一下,禁用最小帧率.然后你就可以使用3个显示APIs中的一个了.传统的present(drawable:)将会在GPU结束渲染一帧画面(在固定帧率显示屏上为16.6ms,在ProMotion显示屏上为4ms)后直接呈现图像.第二个APIpresent(drawable, afterMinimumDuration:)在固定帧率显示屏上提供了帧与帧之间的最大间隔.第三个APIpresent(drawable, atTime:),在创建自定义动画循环时非常有用,或者试图与其它输出,比如音频,同步时非常有用.这里是如何实现它的例子:

let targetTime = 0.1
let drawable = metalLayer.nextDrawable()
commandBuffer.present(drawable, atTime: targetTime)
// after 1-2 frames
let presentationDelay = drawable.presentedTime - targetTime

首先,当你想要展示画面时,设置一个时间,然后渲染场景到一个命令缓冲器中.然后等待下一帧(下几帧),最后检验延迟,这样你就可以调整下一帧的时间.

5). Direct to Display直连显示屏 - 新的方式,用于将渲染器的内容以最小延迟直接发送到外置显示器(如,VR中使用的头戴显示设备).一个画面在GPU结束渲染后到最终出现在显示屏上之前会有两条路径可选.第一条是当系统将其它视图和层混合起来形成最终图像时,典型的UI方案:

[MetalKit]Introducing Metal 2介绍Metal2_第13张图片
DirectToDisplay1.png

当创建一个不包含混合,缩放或其它视图/图层的全屏应用程序时,第二条途径允许显示屏直接访问我们渲染的内存,这样节省了大量系统资源,避免了很多开销:


[MetalKit]Introducing Metal 2介绍Metal2_第14张图片
DirectToDisplay2.png

然而,这需要满足下列条件:

  • 图层是不透明的
  • 没有遮罩或圆角
  • 全屏,或带有不透明的黑色状态栏和背景
  • 被渲染尺寸最大和显示屏尺寸一样大
  • 颜色空间和像素格式与显示屏兼容

颜色空间的要求,使得判断何时Direct to Display模式能使用变得简单.例如,如果你在使用P3显示屏却禁用了P3模式,当试图使用Direct to Display模式时会很容易探测出来.

6). Other Features其它特性 - 包含但不限于:

  • memory usage queries内存使用查询 - 现在有了新的APIs可以在每次分配时查询内存使用,还有设备的总GPU内存分配:
MTLResource.allocatedSize
MTLHeap.currentAllocatedSize
MTLDevice.currentAllocatedSize
  • SIMDGroup scoped functions单指令多数据流(SIMD)群组作用域函数 - 允许数据在SIMD组内被注册者共享,避免加载/储存操作:

    [MetalKit]Introducing Metal 2介绍Metal2_第15张图片
    SIMDGroup.png

  • non-uniform threadgroup sizes非一致性线程组尺寸 - 帮助我们不浪费GPU循环,避免遇到边缘/边界情况:

    [MetalKit]Introducing Metal 2介绍Metal2_第16张图片
    nonuniform.png

  • Viewport Arrays视口数组 - 在macOS上现在支持多达16视口,以供顶点函数在渲染时选择顶点,在VR中结合实例非常有用.

  • Multisample Pattern Control多重采样类型控制 - 允许选择在单个像素中MSAA处于什么采样模式,对于自定义反走样非常有用.

  • Resource Heaps资源堆 - 现在在macOS上也可以使用了.它允许以下时间:控制内存分配,快速重新分配,资源混叠,快速绑定的组相关资源.

  • 其它特性,包括:

Feature特性 Description描述
Linear Textures线性纹理 MTLBuffer创建纹理,无需复制
Function Constant for Argument Indexes为参数索引的函数常量 特殊的二进制编码,供着色器参数改变绑定索引
Additional Vertex Array Formats附加顶点数组格式 添加1个/2个组件的顶点格式,及一个BGRA8顶点格式.
IOSurface Textures IO表面纹理 iOS上从IOSurfaces创建MTLTextures
Dual Source Blending双重来源混合 带有两源参数的附加混合模式

我已经为最重要的新特性制作了一张表,来说明在最新版本的操作系统上是否是新特性.


[MetalKit]Introducing Metal 2介绍Metal2_第17张图片
features.png

最后,还有几行我写的代码,来测试内置与外置GPU之间的不同点:

[MetalKit]Introducing Metal 2介绍Metal2_第18张图片
gpuCompare.png

所有图片都来自WWDC报告.
源代码source code已发布在Github上.
下次见!

你可能感兴趣的:([MetalKit]Introducing Metal 2介绍Metal2)