Metal Shading Language 语法规范

1. Metal Shading Language简介

Metal着色语言是用来编写3D图形渲染逻辑并行计算核心逻辑的一门编程语言,底层使用ClangLLVM进行编译处理。
Metal语言基于C++ 11.0 语言设计,我们主要用来编写在GPU上执行的图像渲染逻辑代码以及通用并行计算逻辑代码。当我们使用Metal框架来完成APP的实现时则需要使用Metal编程语言。

2. Metal 使用常识

2.1 Metal 与 C++ 11.0

Metal Restrictions 限制 (如下的C++11.0的特性在Metal 着色语言中是不支持的):

  • Lambda表达式
  • 递归函数调⽤
  • 动态转换操作符
  • 类型识别
  • 对象创建(new)和释放(delloc)操作符
  • 操作符 noexcept
  • goto跳转
  • 虚函数修饰符-
  • 派⽣类
  • 异常处理

C++的标准库不可以在Metal 着色语言中使用。

2.2 Metal 语言中对于指针使用的限制

  • Metal图形和并行计算函数用到的入参,如果是指针必须使用地址空间修饰符 (device ,threadgroup ,constant)
  • 不支持函数指针
  • Metal函数名不能命名为Main函数

2.3 Metal 与OpenGL ES

  • 在OpenGL ES中,图片的纹理坐标原点是基于左下角的,而在Metal中,苹果为了统一,将图片纹理的坐标原点设在了左上角。
  • 在OpenGL ES中,需要我们手动去编译顶点着色器和片元着色器,而在Metal中,这一切是由苹果系统为我们做的。

3.0 Metal数据类型

类型 描述
bool 布尔类型,取值范围true,false;true可以拓展为整数常量1,false可以拓展为整数常量0
char 有符号8-bit整数
unsigned char (uchar) 无符号8-bit整数
short 有符号16-bit整数
unsigned short (ushort) 无符号16-bit整数
int 有符号32-bit整数
unsigned int (uint) 无符号32-bit整数
half 一个16-bit浮点数
float 一个32-bit浮点数
size-t 64-bit无符号整数,表示sizeof操作符的结果
ptrdiff_t 64-bit有符号整数,表示2个指针的差
void 表示一个空的值集合

3.1 Metal 向量和矩阵数据类型

3.1.1 向量

  • booln
  • charn
  • shortn
  • intn
  • ucharn
  • ushortn
  • uintn
  • halfn
  • floatn
    向量中的n,指的是维度,当n=1时直接省略,n最大是4。
bool2 A= {1,2};
float4 pos = float4(1.0,2.0,3.0,4.0);
float x = pos[0];
float y = pos[1];

float4 VB;
for(int i = 0; i < 4 ; i++)
    VB[i] = pos[i] * 2.0f;


//通过向量字母来获取元素
int4 test = int4(0,1,2,3);
int a = test.x;
int b = test.y;
int c = test.z;
int d = test.w;

int e = test.r;
int f = test.g;
int g = test.b;
int h = test.a;

float4 c;
c.xyzw = float4(1.0f,2.0f,3.0f,4.0f);
c.z = 1.0f;
c.xy = float2(3.0f,4.0f);
c.xyz = float3(3.0f,4.0f,5.0f);


float4 pos = float4(1.0f,2.0f,3.0f,4.0f);
float4 swiz = pos.wxyz;  //swiz = (4.0,1.0,2.0,3.0);
float4 dup = pos.xxyy;  //dup = (1.0f,1.0f,2.0f,2.0f);

//pos = (5.0f,2.0,3.0,6.0)
pos.xw = float2(5.0f,6.0f);

//pos = (8.0f,2.0f,3.0f,7.0f)
pos.wx = float2(7.0f,8.0f);

//pos = (3.0f,5.0f,9.0f,7.0f);
pos.xyz = float3(3.0f,5.0f,9.0f);



float2 pos;
pos.x = 1.0f; //合法
pos.z = 1.0f; //非法

float3 pos2;
pos2.z = 1.0f; //合法
pos2.w = 1.0f; //非法

//非法,x出现2次
pos.xx = float2(3.0,4.0f);
//不合法-使用混合限定符
pos.xy = float4(1.0f,2.0,3.0,4.0);

float4 pos4 = float4(1.0f,2.0f,3.0f,4.0f);
pos4.x = 1.0f;
pos4.y = 2.0f;
//非法,.rgba与.xyzw 混合使用
pos4.xg = float2(2.0f,3.0f);
////非法,.rgba与.xyzw 混合使用
float3 coord = pos4.ryz;

float4 pos5 = float4(1.0f,2.0f,3.0f,4.0f);
//非法,使用指针来指向向量/分量
my_func(&pos5.xy);

3.1.2 矩阵

矩阵支持如下类型:

  • halfnxm
  • floatnxm
    nxm分别指的是矩阵的⾏数和列数,最大支持4行4列
    例:
float4x4 m;
//将第二排的值设置为0
m[1] = float4(2.0f);

//设置第一行/第一列为1.0f
m[0][0] = 1.0f;

//设置第三行第四列的元素为3.0f
m[2][3] = 3.0f;

float4类型向量的所有可能构造方式

float4(float x);
float4(float x,float y,float z,float w);
float4(float2 a,float2 b);
float4(float2 a,float b,float c);
float4(float a,float2 b,float c);
float4(float a,float b,float2 c);
float4(float3 a,float b);
float4(float a,float3 b);
float4(float4 x);

float3类型向量的所有可能的构造的方式

float3(float x);
float3(float x,float y,float z);
float3(float a,float2 b);
float3(float2 a,float b);
float3(float3 x);

float2类型向量的所有可能的构造方式

float2(float x);
float2(float x,float y);
float2(float2 x);

4.纹理Texture类型

纹理类型是一个句柄,它指向一个一维/二维/三维纹理数据。
枚举值:定义了访问权限:

enum class access (sample,read,write);

sample:纹理对象可以被采样;
read:不使用采样器,一个图形渲染函数或者一个并行计算函数可以读取纹理对象;
write:一个图形渲染函数或者一个并行计算函数可以向纹理对象写入数据。

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

T:数据类型,设定了从纹理中读取或者是向纹理中写入时的颜色类型,T可以是half,float,short,int等。
举例

void foo (texture2d<float> imgA [[ texture(0) ]] ,texture2d<float, access::read> imgB [[ texture(1) ]], texture2d<float, access::write> imgC [[ texture(2) ]])
{ 
    ...
}

5. 采样器 Samples

采样器类型决定了如何对一个纹理进行采样操作。在Metal框架中有一个采样器对象MTLSamplerState,这个对象作为图形渲染着色器函数参数或者是并行计算函数的传递参数,也就是在sample中我们设置纹理的采样方式(环绕方式、过滤方式)。在Metal程序中初始化的采样器必须使用constexpr修饰符修饰

  • Metal支持的采样器状态和默认值
枚举名称(Enum Name) 有效值(Valid Value) 描述
coord normalized 从纹理中采样时,纹理坐标是否需要归一化
address clamp_to_edge,clamp_to_zero,mirrored_repeat,repeat 设置所有纹理坐标的寻址模式
s_address,t_address,r_address clamp_to_edge,clamp_to_zero,mirrored_repeat,repeat 设置某个纹理坐标的寻址模式
filter nearest,linear 设置纹理采样的放大和缩小的过滤模式
mag_filter nearest,linear 设置纹理采样的放大过滤模式
min_filter nearest,linear 设置纹理采样的缩小过滤模式
mip_filter none,nearest,linear 设置纹理采样的minmap过滤模式,如果是none,那么只有一个层纹理能生效
compare_func none,less,less_qual,greater,greater_equal,equal,not_equal 为使用r纹理坐标做shaw map,设置比较测试逻辑,这个状态值的设置只可以在Metal着色语言程序中完成

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

enum class coord {noramlized,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 };

例:

constexpr samper s(coord::pixel,address::clamp_to_zero,filter::linear);

6. 函数修饰符

  • kernel ,表示该函数是一个数据并行计算着色函数,它可以被分配在一维/二维/三维线程组中去执行。注意:使用kernel修饰的函数,气返回值类型必须是void类型。
kernel void test(...)
{
    ......
}
  • vertex,表示该函数是一个顶点着色函数,它将为顶点数据流中的每个顶点数据执行一次然后为每个顶点生成数据输出到绘制管线。
  • fragment,表示该函数是一个片元着色函数,它将为片元数据流中的每个片元和其关联执行一次然后将每个片元生成的颜色数据输出到绘制管线中。
    只有图形函数才可以被vertex和fragment修饰。对于图形修饰函数,可以根据返回值判断其是为顶点做计算还是为像素做计算。注意:一个被函数修饰符修饰的函数不能调用另一个函数修饰符修饰的函数。
kernel void test1(...)
{
}
vertex float4 test2(...)
{
  test1(...); // 这个是错误的调用
}

7. 地址空间修饰符

地址空间修饰符,是Metal着色语言用来给参数变量或者函数变量分配内存使用的。所有的着色函数(kernel,vertex,fragment)的参数,如果是指针或者是引用,都必须带有地址空间修饰符。

  • device(设备地址空间)
    在设备地址空间(device)指向设备内存池(这里指的是显存)分配出来的缓存对象,既是可读也是可写的。
device float4 *color;
struct test{
 float a[2];
int b[1];
}
device test *info;

注意:纹理对象总是在设备地址空间分配内存,device地址空间修饰符不必出现在纹理类型定义中,一个纹理对象的内容无法直接访问,可使用Metal自身的内建函数

  • threadgroup(线程组地址空间)
    线程组地址空间用于为并行计算着色函数分配内存变量,这些变量被一个线程组的所有线程共享。在线程组地址空间分配的变量不能被用于图形绘制着色函数。
    在并行计算周瑟函数中,在线程组地址空间分配的变量为一共线程组共同使用,生命周期和线程组相同。
kernel void func(threadgroup float *a[[threadgroup(0)]])
{
    ....
}
  • constant(常量地址空间)
    常量地址空间指向的缓存对象也是从设备内存池分配存储,但是它是只读的。
    在程序域的变量的生命周期和程序一样,在程序中的并行计算着色函数或者图形绘制着色函数使用,但是constant的值是不会变的。
    注意:常量地址空间的指针或是引用可以作为函数的参数。向声明为常量的变量赋值会编译失败,声明常量不赋值也会编译失败。
constant float samples[] = {1.0f,2.0f,3.0f,4.0f};
samples[4] = {3,2,3,2}; //编译失败,因为constant是只读的
constant float a; // 编译失败,因为没有赋值
  • thread(线程地址空间)
    thread地址空间指向每个线程准备的地址空间,这个线程的地址空间定义的变量在其他线程不可见,在图形绘制着色函数或者并行计算着色函数中可使用thread声明。
kernel void func(...)
{
  float x;
  thread float p = &x;
  ...
}

对于图形着色函数,其指针或是引用类型的参数必须定义为device或者是constant地址空间,对于并行计算着色函数,其指针或是引用类型的参数必须定义为device或者是constant或者是threadgroup地址空间

8. 函数参数与变量

图形绘制或者并行计算着色器函数的输入输出都是通过参数传递,除了常量地址空间变量和程序域定义的采样器除外。

  • device buffer - 设备缓存,一个指向设备地址空间的任意数据类型的指针或者引用;
  • constant buffer - 常量缓存区,一个指向常量地址空间的任意数据类型的指针或者引用;
  • texture - 纹理对象;
  • sampler - 采样器对象;
  • threadgroup - 在线程组中供各线程共享的缓存。
    注意:被着色器函数的缓存(device和constant)不能重名。

对于每个着色器函数来说,一个修饰符是必须指定的,他用来设定一个缓存,纹理,采样器的位置。

  • device buffer/constant buffer → [[buffer(index)]]
  • texture → [[texture(index)]]
  • sampler → [[sampler(index)]]
  • threadgroup → [[threadgroup(index)]]
    index是一个unsigned integer类型的值,它表示了一个缓存、纹理、采样器的位置。
kernel void add_vectros(
                const device float4 *inA [[buffer(0)]],
                const device float4 *inB [[buffer(1)]],
                device float4 *out [[buffer(2)]]
                uint id[[thread_position_in_grid]])
{
    out[id] = inA[id] + inB[id];
}
thread_position_in_grid:用于表示当前节点在多线程网格中的位置;

内建变量属性修饰符

  • [[vertex_id]]: 顶点id标识符
  • [[position]]: 顶点信息(float4)表示片元的窗口相对坐标
  • [[point_size]]: 点的大小(float)
  • [[color(m)]]:颜色,m编译前必须要确定
struct myFragmentOutput{
    float4 cl_f[[color(0)]];
    float4 cl_i[[color(1)]];
    float4 cl_u[[color(2)]];
};

fragment myFragmentOutput shader()
{
    myFragmentOutput f;
    f.cl_f = ...;
    ...
    return f;
}
  • [[stage_in]]:片元着色函数使用的单个片元输入数据是由顶点着色函数输出然后经过光栅化生成的。顶点和片元着色函数都是只能有一个参数被声明为使用“stage_in”修饰符,对于一个使用了stage_in修饰符的自定义的结构体,期结构体内部成员可以是整形、浮点型标量、浮点型向量。

属性修饰符目的:

  1. 参数表示资源如何定位? 可以理解为端口;
  2. 在固定管线和可编程管线进行内建变量的传递;
  3. 将数据沿着渲染管线从顶点函数传递片元函数。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容