Metal Metal Shader Language
Metal Shader Language是做什么用的?
Metal着色器语言,是用来编写3D图形渲染逻辑和并行计算核心逻辑的一门编程语言,当你使用Metal框架来完成App的实现,则需要使用Metal编程语言。
Metal语言使用Clang和LLVM进行编译处理。
Metal基于C++11.0语言设计,我们主要用来编写在GPU上执行的图像渲染逻辑代码以及通用并行计算逻辑代码。
C++11.0和Metal语言的异同之处
Metal Restrictions 限制
如下的C++11.0的特性在Metal着⾊器语⾔中是不支持的.
- Lambda表达式
- 递归函数调⽤用
- 动态转换操作符
- 类型识别
- 对象创建(
new
)和释放(delloc
)操作符 - 操作符
noexcept
-
goto
跳转 - 虚函数修饰符
- 派⽣生类
- 异常处理理
- 不支持函数指针
- C++的标准库不可以在Metal着色语言中使用
Metal 语言中对于指针使用的限制
- Metal图形和并行计算函数用到的入参,如果是指针必须使用地址空间修饰符 (device ,threadgroup ,constant)修饰。
- 不支持函数指针。
- Metal函数名不能命名为Main函数。
Metal 像素坐标系统
Metal中纹理/帧缓存区attachment的像素使用的坐标系统的原点是左上角。
Metal 数据类型
类型 | 释义 | 取值范围 |
---|---|---|
bool | 布尔类型 | true/false |
char | 有符号8位整数 | |
unsigned char/uchar | 无符号8位整数 | |
short | 有符号16位整数 | |
unsigned short/ushort | 无符号32位整数 | |
half | 16位浮点数 | |
float | 32位浮点数 | |
size_t | 64位无符号整数 | |
void | 该类型表示一个空的值集合 |
Metal支持后缀用字面量表示数据类型。例如0。5F,0.5f,0.5h,0.5H。
Metal Textures类型
纹理类型是一个句柄,它指向一个一维/二维/三维纹理数据。
在一个函数中描述纹理对象类型。
定义访问权限的枚举
enum class access {sample, read, write};
sample:纹理对象可以被采样。
read:不使用采样器,一个图像渲染函数或者一个并行计算函数可以读取纹理对象。
write:一个图形渲染函数或者一个并行计算函数可以向纹理对象写入数据。
texture1d
texture2d
texture3d
T:表示数据类型,设定了从纹理中读取或者向纹理中写入时颜色的类型,T可以是half,float,short,int等;
示例代码
void foo(texture1d imageA [[texture(0)]], texture2d imageB[[texture(1)]], texture3d imageC[[texture(2)]]) {
...
}
采样器类型Samplers
采样器类型决定了如何对一个纹理进行采样操作,在Matel框架中有一个对应的
着色器语言的采样器对象MLTSampleState
这个对象作为图形渲染着色器函数参数或者并行计算函数的参数传递。
从纹理中采样时,纹理坐标是否需要归一化。
enum class coord {normalized, pixel}
纹理采样过滤方式方法/缩小过滤模式。
enum class filter {nearest, linear}
设置纹理采样的缩小过滤模式。
enum class min_filter {nearest, linear}
设置纹理采样的方法过滤模式。
enum class mag_filter {nearest, linear}
设置纹理s,t,r坐标的寻址模式。
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}
设置所有纹理坐标的寻址模式。
enum class address {clamp_to_zero, clamp_to_edge, repeat, mirrored_repeat}
设置纹理采样的mipMap过滤模式,如果是none,那么只有一层纹理生效。
enum class mip_filter {none, nearest, linear}
在Metal程序中初始化的采样器必须使用constexpr
修饰符声明。
constexpr sampler s(coord::pixel, address::clamp_to_zero, filter::linear);
constexpr sampler a(coord::normalized);
constexpr sampler b(address::repeat);
constexpr sampler s(address::clamp_to_zero, filter::linear);
函数修饰符
Metal有以下三种修饰符
- kernel:表示该函数是一个数据并行计算着色函数,他可以被分配在一维/二维/三维线程组中去执行。
- vertex:表示该函数是一个顶点着色函数,它将为顶点数据流中的每个顶点数据执行一次,然后为每个顶点生产数据输出到绘制管线。
- fragment:表示该函数是一个片元着色函数,它将为片元数据流中的每个片元和其他关联执行一次后,将每个片元生成的颜色数据输出到绘制管线中。
kernel void foo(...) {
...
}
注意:使用kernel修饰的函数,其返回值类型必须是void类型。
只有图形着色函数才可以被vertex和fragment修饰。对于图形着色函数,返回值类型可以辨认出它是为顶点做计算还是为每个像素做计算。图形着色函数的返回值可以为void,但是这也就意味着该函数不产生数据输出到绘制管线,这是一个无意义的动作。
注意:一个被函数修饰符修饰的函数,不能被其他也被函数修饰符修饰的函数调用,这样会导致编译失败。
kernel void hello2(...) {
}
vertex float4 hello1(...) {
// 错误调用
hello2(...);
}
地址空间修饰符
用于变量或是参数的修饰符
Metal着色器语言使用地址空间修饰符来表示一个函数变量或者参数变量。所有着色器函数(kernel,vertex,fragment)的参数,如果是指针或是引用,都必须带有地址空间修饰符号。
- device
- threadgroup
- constant
- thread
对于图形着色器函数,其指针或是引用类型的参数必须定义为device或是constant地址空间;
对于并行计算着色器函数,其指针或是引用类型的参数必须定义为device或是threadgroup或是constant;
Device Address Space(设备地址空间)
在设备地址空间指向设备内存池分配出来的缓存对象,它是可读也是可写的,一个缓存对象可以被声明成一个标量,向量或者是用户自定义的结构体的指针或是引用。
device float4 *color;
struct Foo {
float a[3];
int b[2];
};
device Foo *my_info;
注意:纹理对象总是在设备地址空间分配内存,device地址空间修饰符不必出现在纹理对象的定义中,一个纹理对象的内容无法直接访问,Metal提供读写纹理的内建函数。
threadgroup Address Space(线程组地址空间)
线程组地址空间用于为并行计算着色函数分配内存变量,这些变量被一个线程组的所有线程共享,在线程组地址空间分配的变量不能被用于图形绘制着色器函数【顶点着色器函数,片元着色器函数】。
在并行着色函数中,在线程组地址空间分配的变量为一个线程组使用,生命周期和线程相同。
kernel void my_func(threadgroup float *a[[threadgroup(0)]], ...) {
threadgroup float x;
threadgroup float b[10];
}
constant Address Space(常量地址空间)
常量地址空间指向的缓存对象也是设备内存池分配存储,但它是只读的。
在程序域的变量必须定义在常量地址空间,并且在声明时初始化。用来初始化的值必须是编译时的常量。
在程序域变量的生命周期和程序一样,在程序中的并行计算着色函数或者图形绘制着色函数中调用,但是constant的值会保持不变。
注意:常量地址空间的指针或是引用可以作为函数的参数,向声明为常量的变量赋值会产生编译错误,声明常量但是没有赋予初值也会产生编译错误。
constant float samplers[] = {1.0f, 2.0f, 3.0f, 4.0f};
对一个常量地址空间的变量进行修改也会失败,因为它是只读的。
sampler[4] = {3, 3, 3, 3};
定义为常量地址空间声明时不赋初值也会编译失败。
constant float a;
thread Address Space(线程地址空间)
线程地址空间指向每个线程准备的地址空间,这个线程的地址空间定义的变量在其他线程不可见,在图形绘制着色函数或者并行计算着色函数声明的变量thread地址空间分配。
kernel void my_func(...) {
float x;
thread float p = &x;
...
}
函数参数与变量
图形绘制或者并行计算着色器函数的输入输出都是通过参数传递,除了常量地址空间变量和程序域定义的采样器以外:
- device buffer:设备缓存,一个指向设备地址空间的任意数据类型的指针或者引用。
- constant buffer:常量缓冲区,一个指向常量地址空间的任意数据类型的指针或者引用。
- texture:纹理对象。
- sampler:采样器对象。
- threadGroup:在线程组中供各线程共享的缓存。
注意:着色器函数的缓存(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类型的值,它表示了一个缓存、纹理、采样器参数的位置(在函数参数索引表中的位置)。从语法上讲,属性修饰符的声明位置应该位于参数变量名之后。
下面的例子展示了一个简单的并行计算着色函数add_vectors
,它把两个设备地址空间中的缓存inA和inB相加,然后把结果写入到缓存out。属性修饰符buffer(index)为着色函数参数设定的参数的位置。
thread_position_in_grid:用于表示当前节点在多线程网格中的位置。
kernel void add_vectors(const device float4 *inA[[buffer(0)]], const device float4 *inB[[burrer(1)]], device float4 *out[[buffer(2)]], uint id[[thread_position_in_grid]]) {
out[id] = inA[id] + inB[id];
}
内建变量属性修饰符
-
[[vertex_id]]
:顶点id标识符。 -
[[position]]
:顶点信息(float4)。
-
[[point_size]]
:点的大小(float)。 -
[[color(m)]]
:颜色,m编译前需要确定。 -
[[stage_in]]
:片元着色函数使用的单个片元输入数据是由顶点着色函数输出然后经过光栅化生成的,顶点和片元着色函数都是只能有一个参数被声明为使用“stage_in”修饰符。对于一个使用了“stage_in”修饰符的自定义的结构体,其成员可以为一个整形或者浮点标量,或是整形或浮点向量。
struct MyfragmentOutput {
float4 clr_f [[color(0)]];
int4 clr_i [[color(1)]];
uint4 clr_ui [[color(2)]];
};
fragment MyfragmentOutput my_fragment_shader(...) {
MyfragmentOutput f;
...
f.clr_f = ...;
...
return f;
}