本系列文章是对 metalkit.org 上面MetalKit内容的全面翻译和学习.c++
今年的WWDC
多是有史以来最重要的,至少目前咱们 - Metal
开发者 - 终于被关注了.我能够诚心诚意地讲这是我生命中最棒的一周!github
让咱们看看Games and Graphics游戏和图形新闻.最意想不到
的是,将Metal
重命名为Metal 2.自从它在2014年
第一次被发布后,它终于有了最显著的增长和加强,确实,但咱们必须认可:没人预见到这一变化的来临.最意想不到
的奖品是新的ARKit框架.在keynote演示以后仅仅几周,就已经有了大量大胆有趣的加强现实项目放出来. ARKit融入Metal
很是容易.最后,最有影响力
的奖品是VR.这是由于虚拟现实如今可以实现更低延迟,更高帧率,还有更强劲的内置显卡,如今也有了外置显卡external GPUs.swift
Model I/O
, SpriteKit
和SceneKit
框架也都添加了新特性.其它使人感兴趣的增长是用于机器学习 machine learning的CoreML
和Vision
框架.本文只关注Metal
中的新特点:数组
1). MPS - Metal Performance Shaders目前在macOS
上也可使用了,添加的新特性包括:网络
Image Keypoints图像关键点
, Bilinear Rescale双线性重缩放
, Image Statistics图像统计
, Element-wise Arithmetic Operations元素运算符
).MPSVector
, MPSMatrix
和MPSTemporaryMatrix
,还有*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参数缓冲器/变元缓冲器 - 多是今年对框架最重要的添加.在传统的加强模型中,咱们会为每一个对象调用许多函数来设置缓冲器,纹理,线性采样,最后为该对象调用绘制命令. app
正如你想象的那样,,当将该数量乘以物体总数量及绘制帧数时,调用数急剧增加.最终这会限制屏幕上出现的物体数量. 框架
Argument Buffers参数缓冲器
引入了一个高效的新途径来使用资源,经过采用始终存在的indirect behavior间接行为,来将其应用到纹理,采样,状态,指向其它缓冲器的指针,等等.参数缓冲器将只有每物体2个API调用
:设置参数缓冲器,而后绘制.这样能够绘制更多物体. 机器学习
参数缓冲器很容易使用,就像匹配着色器数据和主机数据同样:函数
struct Material {
float intensity;
texture2d<float> aTexture;
sampler aSampler;
}
kernel void compute(constant Material &material [[ buffer(0) ]]) {
...
}
复制代码
在CPU
上,参数缓冲器是被MTLArgumentEncoder对象建立和使用的,而后它能够轻易地在CPU
和GPU
之间作位块传送:
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调用
:一个设置到缓冲器,一个为大量实例绘制索引的基本体!
另外一个应用参数缓冲器的例子是,当运行粒子模拟时.对此,咱们有resource setting on the GPU
特性,它的意思是有一个参数缓冲器数组,每一个粒子(线程)一个缓冲器.全部的粒子特性(位置,材料,等)在GPU
上的参数缓冲器中被建立和储存的,这样当一个粒子须要某个属性时,好比材料,它将从参数缓冲器中复制出来,而再也不须要从CPU
获取,这样能够避免昂贵的CPU
与GPU
之间的复制开销.
复制内核很简单,可让你赋值一个常数,从源对象部分复制或者彻底复制到目标对象:
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
命令来保证.
在传统混合模式下,要建立竞争条件:
fragment void blend(texture2d<float, access::read_write> 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<float, access::read_write>
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
时典型的帧率是60
FPS(16.6
ms/frame):
使用ProMotion
后帧率提高到120
FPS(8.3
ms/frame),这对于用户输入很是有用,如手指触摸或pencil使用:
ProMotion
也提供了弹性机制来刷新显示图片,因此咱们不须要使用固定帧率.不使用ProMotion
会有图片刷新不一致,这对用户体验很很差.开发者为了实现一致性,一般会将峰值帧率强制保持在30
FPS,而不是理想的48
FPS(20.83
ms/frame):
使用ProMotion
我如今能够每4
ms就有一个刷新点,而再也不是每16
ms(竖直的白线):
ProMotion
一样有助于掉帧.不使用ProMotion
时,可能一帧画面花了太长时间来显示,就错过了截止期限:
ProMotion
经过对帧扩展4
ms而不是一个完整帧(16.6
ms)来解决这个问题:
UIKit
动画自动使用ProMotion
,可是在Metal
视图中使用ProMotion
,你须要在项目的Info.plist
文件中设置一下,禁用最小帧率.而后你就可使用3个显示APIs
中的一个了.传统的present(drawable:) 将会在GPU
结束渲染一帧画面(在固定帧率显示屏上为16.6
ms,在ProMotion
显示屏上为4
ms)后直接呈现图像.第二个API
是present(drawable, afterMinimumDuration:) 在固定帧率显示屏上提供了帧与帧之间的最大间隔.第三个API
是present(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
方案:
当建立一个不包含混合,缩放或其它视图/图层的全屏应用程序时,第二条途径容许显示屏直接访问咱们渲染的内存,这样节省了大量系统资源,避免了不少开销:
然而,这须要知足下列条件:
颜色空间的要求,使得判断什么时候Direct to Display
模式能使用变得简单.例如,若是你在使用P3
显示屏却禁用了P3
模式,当试图使用Direct to Display
模式时会很容易探测出来.
6). Other Features其它特性 - 包含但不限于:
APIs
能够在每次分配时查询内存使用,还有设备的总GPU
内存分配:MTLResource.allocatedSize
MTLHeap.currentAllocatedSize
MTLDevice.currentAllocatedSize
复制代码
SIMDGroup scoped functions单指令多数据流(SIMD)群组做用域函数 - 容许数据在SIMD
组内被注册者共享,避免加载/储存操做:
non-uniform threadgroup sizes非一致性线程组尺寸 - 帮助咱们不浪费GPU
循环,避免遇到边缘/边界状况:
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双重来源混合 | 带有两源参数的附加混合模式 |
我已经为最重要的新特性制做了一张表,来讲明在最新版本的操做系统上是不是新特性.
最后,还有几行我写的代码,来测试内置与外置GPU
之间的不一样点:
全部图片都来自WWDC
报告. 源代码source code已发布在Github上.
下次见!