本系列文章是对 http://metalkit.org 上面MetalKit内容的全面翻译和学习.
MetalKit系统文章目录
今年的WWDC
可能是有史以来最重要的,至少目前我们 - Metal
开发者 - 终于被关注了.我可以诚心诚意地讲这是我生命中最棒的一周!
让我们看看Games and Graphics游戏和图形新闻.最意想不到
的是,将Metal
重命名为Metal 2.自从它在2014年
第一次被发布后,它终于有了最显著的增加和增强,确实,但我们必须承认:没人预见到这一变化的来临.最意想不到
的奖品是新的ARKit框架.在keynote演示之后仅仅几周,就已经有了大量大胆有趣的增强现实项目放出来. ARKit融入Metal
非常容易.最后,最有影响力
的奖品是VR.这是因为虚拟现实现在能够实现更低延迟,更高帧率,还有更强劲的内置显卡,现在也有了外置显卡external GPUs.
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参数缓冲器/变元缓冲器 - 可能是今年对框架最重要的添加.在传统的增强模型中,我们会为每个对象调用许多函数来设置缓冲器,纹理,线性采样,最后为该对象调用绘制命令.
正如你想象的那样,,当将该数量乘以物体总数量及绘制帧数时,调用数急剧增长.最终这会限制屏幕上出现的物体数量.
Argument Buffers参数缓冲器
引入了一个高效的新途径来使用资源,通过采用始终存在的indirect behavior间接行为,来将其应用到纹理,采样,状态,指向其它缓冲器的指针,等等.参数缓冲器将只有每物体2个API调用
:设置参数缓冲器,然后绘制.这样可以绘制更多物体.
参数缓冲器很容易使用,就像匹配着色器数据和主机数据一样:
struct Material {
float intensity;
texture2d 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 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
时典型的帧率是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其它特性 - 包含但不限于:
- memory usage queries内存使用查询 - 现在有了新的
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上.
下次见!