Metal Shading Language

262 阅读8分钟

注:本文旨在记录笔者的学习过程,仅代表笔者个人的理解,如果有表述不准确的地方,欢迎各位指正!因为涉及到的概念来源自网络,所以如有侵权,也望告知!

前言

本文的主要目的是带大家了解Metal程序中的Metal着⾊语⾔的语法规则。

正文

一、Metal 着色语⾔

        Metal 着⾊语⾔是⼀个⽤来编写3D图形渲染逻辑和并⾏计算核⼼逻辑的编程语⾔,编写Metal 框架的APP需要使⽤Metal 着⾊语⾔程序。Metal 着⾊语⾔与Metal 框架配合使⽤,Metal 框架管理Metal 着⾊语⾔的运⾏和可选编译选项。 Metal 着⾊器语⾔使⽤Clang和LLVM,编译器对于在GPU上的代码执⾏效率有更好的控制。
        Metal 这⻔语⾔是基于C++ 11.0标准设计的。它在C++基础是⾏多了⼀些拓展和限制。下⾯我们可以简单介绍介绍Metal 着⾊语⾔与C++11.0 相⽐之下的修改和限制。
Metal Restrictions 限制(如下的C++11.0的特性在Metal 着⾊语⾔中是不⽀持的):
  • Lambda表达式
  • 递归函数调⽤
  • 动态转换操作符
  • 类型识别
  • 对象创建(new)和释放(delloc)操作符
  • 操作符 noexcept
  • goto跳转
  • 虚函数修饰符
  • 派⽣类
  • 异常处理

二、Metal 语法介绍

1、Metal 数据类型--标量数据类型

  • bool 布尔类型, true/false
  • char 有符号8位整数;
  • unsigned char /uchar ⽆符号8-bit 整数;
  • short 有符号16-bit整数;
  • unsigned short / ushort ⽆符号32-bit 整数;
  • half 16位bit 浮点数;
  • float 32bit 浮点数;
  • size_t 64 ⽆符号整数;
  • void 该类型表示⼀个空的值集合;

注:Metal ⽀持后缀表示字⾯量类型, 例如 0.5F, 0.5f; 0.5h, 0.5H;

2、Metal 向量和矩阵数据类型

向量⽀持如下类型:
  • booln
  • charn
  • shortn
  • intn
  • ucharn
  • ushortn
  • uintn
  • halfn
  • floatn
注:向量中的n,指的是维度

矩阵⽀持如下类型:

  • halfnxm
  • floatnxm

注:nxm分别指的是矩阵的⾏数和列数

3、纹理Textures 类型

纹理类型是⼀个句柄,它指向⼀个⼀维/⼆维/三维纹理数据,在⼀个函数中描述纹理对象的类型。

其中access枚举值: 定义了访问权利;

enum class access {sample ,read ,write};

  • sample : 纹理对象可以被采样. 采样⼀维这是使⽤或不使⽤采样器从纹理中读取数据;
  • read : 不使⽤采样器, ⼀个图形渲染函数或者⼀个并⾏计算函数可以读取纹理对象;
  • write: ⼀个图形渲染函数或者⼀个并⾏计算函数可以向纹理对象写⼊数据;
示例:

texture1d<T, access a = access::sample>

texture2d<T, access a = access::sample>

texture3d

T : 数据类型 设定了从纹理中读取或是向纹理中写⼊时的颜⾊类型. T可以是half, float, short, int 等; 

4、采样器类型 Samplers

采取器类型决定了如何对⼀个纹理进⾏采样操作。在Metal 框架中有⼀个对应着⾊器语⾔的采样器的对象MTLSamplerState 这个对象作为图形渲染着⾊器函数参数或是并⾏计算函数的参数传递。

Metal支持的采样器参数:

  • enum class coord { normalized, pixel };

        从纹理中采样时,纹理坐标是否需要归⼀化;

  • enum class filter { nearest, linear };

        纹理采样过滤⽅式, 放⼤/缩⼩过滤模式;

  • enum class min_filter { nearest, linear };

        设置纹理采样的缩⼩过滤模式;

  • enum class mag_filter { nearest, linear };

        设置纹理采样的放⼤过滤模式;

  • enum class s_address { clamp_to_zero, clamp_to_edge, repeat, mirrored_repeat };
  • enum class t_address { clamp_to_zero, clamp_to_edge, repeat, mirrored_repeat };
  • enum class r_address { clamp_to_zero, clamp_to_edge, repeat, mirrored_repeat };

        设置纹理s,t,r坐标的寻址模式;

  • enum class address { clamp_to_zero, clamp_to_edge, repeat, mirrored_repeat };

        设置所有的纹理坐标的寻址模式;

  • enum class mip_filter { none, nearest, linear };

        设置纹理采样的mipMap过滤模式, 如果是none,那么只有⼀层纹理⽣效;

注:在Metal 程序中初始化的采样器必须使⽤ constexpr 修饰符声明 

5、函数修饰符

Metal 有以下3种函数修饰符:
  • kernel , 表示该函数是⼀个数据并⾏计算着⾊函数. 它可以被分配在⼀维/⼆维/三维线程组中去执⾏;
  • vertex , 表示该函数是⼀个顶点着⾊函数 , 它将为顶点数据流中的每个顶点数据执⾏⼀次然后为每个顶点⽣成数据输出到绘制管线;
  • fragment , 表示该函数是⼀个⽚元着⾊函数, 它将为⽚元数据流中的每个⽚元 和其关联执⾏⼀次然后将每个⽚元⽣成的颜⾊数据输出到绘制管线中; 
使用函数修饰符的注意点:
  • 使⽤kernel 修饰的函数. 其返回值类型必须是void 类型;
  • 只有图形着⾊函数才可以被 vertex 和 fragment 修饰. 对于图形着⾊函数, 返回值类型可以辨认出它是为顶点做计算还是为每像素做计算. 图形着⾊函数的返回值可以为 void , 但是这也就意味着该函数不产⽣数据输出到绘制管线; 这是⼀个⽆意义的动作;
  • ⼀个被函数修饰符修饰的函数不能在调⽤其他也被函数修饰符修饰的函数; 这样会导致编译失败;

6、⽤于变量或者参数的地址空间修饰符

Metal 着⾊器语⾔使⽤地址空间修饰符来表示⼀个函数变量或者参数变量被分配于那⼀⽚内存区域。所有的着⾊函数(vertex, fragment, kernel)的参数,如果是指针或是引⽤,都必须带有地址空间修饰符号:
  • device
  • threadgrounp
  • constant
  • thread
注:对于图形着⾊器函数, 其指针或是引⽤类型的参数必须定义为 device 或是 constant 地址空间;对于并⾏计算着⾊函数, 其指针或是引⽤类型的参数必须定义为 device 或是 threadgrounp或是 constant 地址空间; 

a、Device Address Space(设备地址空间)

在设备地址空间(Device) 指向设备内存池分配出来的缓存对象, 它是可读也是可写的; ⼀个缓存对象可以被声明成⼀个标量,向量或是⽤户⾃定义结构体的指针或是引⽤.
注意: 纹理对象总是在设备地址空间分配内存, device 地址空间修饰符不必出现在纹理类型定义中. ⼀个纹理对象的内容⽆法直接访问. Metal 提供读写纹理的内建函数; 

b、threadgrounp Address Space(线程组地址空间)

线程组地址空间⽤于为 并⾏计算着⾊函数分配内存变量. 这些变量被⼀个线程组的所有线程共享. 在线程组地址空间分配的变量不能被⽤于图形绘制着⾊函数[顶点着⾊函数, ⽚元着⾊函数]
在并⾏计算着⾊函数中, 在线程组地址空间分配的变量为⼀个线程组使⽤, 声明周期和线程组相同; 

c、constant Address Space(常量地址空间)

常量地址空间指向的缓存对象也是从设备内存池分配存储, 但是它是只读的;
在程序域的变量必须定义在常量地址空间并且声明的时候初始化; ⽤来初始化的值必须是编译时的常量.
在程序域的变量的⽣命周期和程序⼀样, 在程序中的并⾏计算着⾊函数或者图形绘制着⾊函数调⽤, 但是constant 的值会保持不变;
注意: 常量地址空间的指针或是引⽤可以作为函数的参数. 向声明为常量的变量赋值会产⽣编译错误.声明常量但是没有赋予初值也会产⽣编译错误; 

d、thread Address Space(线程地址空间)

thread 地址空间指向每个线程准备的地址空间, 这个线程的地址空间定义的变量在其他线程不可⻅, 在图形绘制着⾊函数或者并⾏计算着⾊函数中声明的变量thread 地址空间分配; 

7、函数参数与变量

图形绘制或者并⾏计算着⾊器函数的输⼊输出都是通过参数传递(除了常量地址空间变量和程序域定义的采样器以外)。参数可以是如下之⼀:
  • device buffer- 设备缓存, ⼀个指向设备地址空间的任意数据类型的指针或者引⽤;
  • constant buffer -常量缓存区, ⼀个指向常量地址空间的任意数据类型的指针或引⽤
  • texture - 纹理对象;
  • sampler - 采样器对象;
  • threadGrounp - 在线程组中供各线程共享的缓存.
注意: 被着⾊器函数的缓存(device 和 constant) 不能重名;

Attribute Qualifiers to Locate Buffers, Textures, and Samplers ⽤于寻址缓存,纹理,采样器的属性修饰符。对于每个着⾊器函数来说,⼀个修饰符是必须指定的。他⽤来设定⼀个缓存,纹理, 采样器的位置,如:

  • device buffers/ constant buffer --> [[buffer (index)]]
  • texture -- [[texture (index)]]
  • sampler -- [[sampler (index)]]
  • threadgroup buffer -- [[threadgroup (index)]]
注:index是⼀个unsigned integer类型的值,它表示了⼀个缓存、纹理、采样器参数的位置(在函数参数索引表中的位置)。 从语法上讲,属性修饰符的声明位置应该位于参数变量名之后。

8、内建变量属性修饰符

  • [[vertex_id]] 顶点id 标识符;
  • [[position]] 顶点信息(float4) / 述了⽚元的窗⼝相对坐标(x, y, z, 1/w);
  • [[point_size]] 点的⼤⼩(float);
  • [[color(m)]] 颜⾊, m编译前得确定;
  • [[stage_in]] : ⽚元着⾊函数使⽤的单个⽚元输⼊数据是由顶点着⾊函数输出然后经过光栅化⽣成的.顶点和⽚元着⾊函数都是只能有⼀个参数被声明为使⽤“stage_in”修饰符,对于⼀个使⽤了“stage_in”修饰符的⾃ 定义的结构体,其成员可以为⼀个整形或浮点标量,或是整形或浮点向量 ;