Metal Shader Language

Metal Shading Language介绍

  Metal 着⾊语⾔ 是⽤来编写 3D 图形渲染逻辑 和 并⾏计算核⼼逻辑的⼀⻔编程语⾔. 当你使⽤Metal 框架来完成APP 的实现时则需要使⽤Metal 编程语⾔。

  Metal 语⾔使⽤Clang 和 LLVM 进⾏编译处理~Metal 基于C++ 11.0 语⾔设计.我们主要⽤来编写 在 GPU 上执⾏的图像渲染逻辑代码以及通用并⾏计算逻辑代码。

Metal 与 C++ 11.0

  Metal 这⻔语⾔是基于C++ 11.0标准设计的.它在C++基础是⾏多了⼀些拓展和限制.下⾯我们可以简单介绍介绍Metal着⾊语⾔与C++11.0 相⽐之下的修改和限制.

  1. C++ 11.0 特性在Metal 语⾔中不⽀持的情况
    • Lambda 表达式;
    • 递归函数调⽤
    • 动态转换操作符
    • 类型识别
    • 对象创建new 和销毁delete 操作符;
    • 操作符 noexcept
    • goto 跳转
    • 变量存储修饰符register 和 thread_local;
    • 虚函数修饰符;
    • 派⽣类
    • 异常处理
    • C++ 标准库在Metal 语⾔中也不可使⽤;
  2. Metal 语⾔中对于指针使⽤的限制
    • Metal图形和并⾏计算函数⽤到的⼊参数; 如果是指针必须使⽤地址空间修饰符(device,threadgroup,constant)
    • 不⽀持函数指针;
    • 函数名不能出现main
  3. Metal 像素坐标系统

    Metal 中纹理/帧缓存区attachment的像素使⽤的坐标系统的原点是左上⻆。

Metal数据类型

标量数据类型

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

类型 描述
bool 布尔类型, true/false
char 有符号8位整数
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 32bit 浮点数
size_t ⽆符号整数
void 该类型表示⼀个空的值集合
向量数据类型

向量中的n,指的是维度。

  • booln
  • charn
  • shortn
  • intn
  • ucharn
  • ushortn
  • uintn
  • halfn
  • floatn纹理Textures 类型:纹理Textures 类型:
向量的访问

int4 test = int4(0,1,2,3);

  1. 通过索引方式访问test[n]

  2. 通过向量字母获取元素xyzwrgba,只取部分、乱序取均可。

    例:

    test.x = 3;
    test.xyz = int3(1,2,3);
    test.rgb = int3(1,2,3);
    
矩阵数据类型

矩阵的行数n,矩阵的列数m

  • halfnxm
  • floatnxm

例:

float4x4 m;
//将第二排的值设置为0
m[1] = float4(2.0f);
//设置第一行/第一列为1.0f
m[0][0] = 1.0f;
//设置第三行第四列的元素为3.0f
m[2][3] = 3.0f;
  1. 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);
    
  2. float3类型向量的所有可能的构造的方式

    float3(float x);
    float3(float x,float y,float z);
    float3(float a,float2 b);
    float3(float2 a,float b);
    float3(float3 x);
    
  3. float2类型向量的所有可能的构造的方式

    float2(float x);
    float2(float x,float y);
    float2(float2 x);
    
纹理Textures 类型

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

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) ]])
{

}
采样器类型 Samplers

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

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

    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 有以下3种函数修饰符:

  • kernel

    表示该函数是⼀个数据并⾏计算着⾊函数. 它可以被分配在⼀维/⼆维/三维线程组中去执⾏,其返回值类型必须为void

    kernel void CCTestKernelFunctionA(int a,int b)
    {
    
        /*
         注意:
         1. 使用kernel 修饰的函数返回值必须是void 类型
         2. 一个被函数修饰符修饰过的函数,不允许在调用其他的被函数修饰过的函数. 非法
         3. 被函数修饰符修饰过的函数,只允许在客户端对齐进行操作. 不允许被普通的函数调用.
         */
    }
    
  • vertex

    表示该函数是⼀个顶点着⾊函数 , 它将为顶点数据流中的每个顶点数据执⾏⼀次然后为每个顶点⽣成数据输出到绘制管线。

    //顶点函数
    vertex int CCTestVertexFunctionB(int a,int b){
    
    }
    
  • fragment

    表示该函数是⼀个⽚元着⾊函数, 它将为⽚元数据流中的每个⽚元和其关联执⾏⼀次然后将每个⽚元⽣成的颜⾊数据输出到绘制管线中。

    //片元函数
    fragment int CCTestVertexFunctionB(int a,int b){
    
    }
    

注意:⼀个被函数修饰符修饰的函数不能在调⽤其他也被函数修饰符修饰的函数; 这样会导致编译失败;

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

  Metal 着⾊器语⾔使⽤ 地址空间修饰符 来表示⼀个函数变量或者参数变量 被分配于那⼀⽚内存区域.

  所有的着⾊函数(vertex, fragment, kernel)的参数,如果是指针或是引⽤, 都必须带有地址空间修饰符号;

  • device
  • threadgrounp
  • constant
  • thread

  对于图形着⾊器函数, 其指针或是引⽤类型的参数必须定义为device 或是 constant地址空间;

  对于并⾏计算着⾊函数, 其指针或是引⽤类型的参数必须定义为 device或是 threadgrounp 或是constant地址空间;

Device Address Space(设备地址空间)

  Device是比较通用的访问模式,使用限制比较少,在设备地址空间(Device) 指向设备内存池分配出来的缓存对象,它是可读也是可写的; ⼀个缓存对象可以被声明成⼀个标量,向量或是⽤户⾃定义结构体的指针或是引⽤。

示例

device float4 *color; 
struct Foo { 
float a[3]; 
int b[2]; 
}; 
device Foo *my_info;


纹理对象总是在设备地址空间分配内存, device 地址空间修饰符不必出现在纹理类型定义中. ⼀个纹理对象的内容⽆法直接访问。而是由Metal 提供读写纹理的内建函数;

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

  线程组地址空间⽤于为 并⾏计算着⾊函数分配内存变量。 这些变量被⼀个线程组的所有线程共享。在线程组地址空间分配的变量不能被⽤于图形绘制着⾊函数(顶点着⾊函数, ⽚元着⾊函数)

  在并⾏计算着⾊函数中, 在线程组地址空间分配的变量为⼀个线程组使⽤, 声明周期和线程组相同;

示例


/*
 1. threadgroup 被并行计算计算分配内存变量, 这些变量被一个线程组的所有线程共享. 在线程组分配变量不能被用于图像绘制.
 2. thread 指向每个线程准备的地址空间. 在其他线程是不可见切不可用的
 */
kernel void CCTestFouncitionF(threadgroup float *a)
{
    //在线程组地址空间分配一个浮点类型变量x
    threadgroup float x;

    //在线程组地址空间分配一个10个浮点类型数的数组y;
    threadgroup float y[10];

}
constant Address Space( 常量地址空间)

  常量地址空间指向的缓存对象也是从设备内存池分配存储, 但是它是只读的,并且限定大小。在程序域的变量必须定义在常量地址空间并且声明的时候初始化,⽤来初始化的值必须是编译时的常量。

  在程序域的变量的⽣命周期和程序⼀样, 在程序中的并⾏计算着⾊函数或者图形绘制着⾊函数调⽤, 但是constant 的值会保持不变。

注意:

  1. 常量地址空间的指针或是引⽤可以作为函数的参数, 向声明为常量的变量赋值会产⽣编译错误。
  2. 声明常量但是没有赋予初值也会产⽣编译错误。

示例

constant float samples[] = { 1.0f, 2.0f, 3.0f, 4.0f };
thread Address Space (线程地址空间)

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

示例

kernel void CCTestFouncitionG(void)
{
    //在线程空间分配空间给x,p
    float x;
    thread float p = &x;

}
函数参数与变量

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

  • 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类型的值,它表示了⼀个缓存、纹理、采样器参数的位置(在函数参数索引表中的位置)。 从语法上讲,属性修饰符的声明位置应该位于参数变量名之后。

示例

//并行计算着色器函数add_vectros ,实现2个设备地址空间中的缓存A与缓存B相加.然后将结果写入到缓存out.
//属性修饰符"(buffer(index))" 为着色函数参数设定了缓存的位置
//并行计算着色器函数add_vectros ,实现2个设备地址空间中的缓存A与缓存B相加.然后将结果写入到缓存out.
//属性修饰符"(buffer(index))" 为着色函数参数设定了缓存的位置
//thread_position_in_grid : ⽤于表示当前节点在多线程⽹格中的位置;
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];
}
内建变量属性修饰符
  • [[vertex_id]]顶点id 标识符;
  • [[position]]顶点信息(float4) /� 描述了⽚元的窗⼝相对坐标(x, y, z, 1/w)
  • [[point_size]]点的⼤⼩,float数据类型
  • [[color(m)]] 颜⾊, m编译前得确定;
  • [[stage_in]] : ⽚元着⾊函数使⽤的单个⽚元输⼊数据是由顶点着⾊函数输出然后经过光栅化⽣成的.顶点和⽚元着⾊函数都是只能有⼀个参数被声明为使⽤stage_in修饰符对于⼀个使⽤了stage_in修饰符的⾃定义的结构体,其成员可以为⼀个整形或浮点标量,或是整形或浮点向量。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,837评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,551评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,417评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,448评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,524评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,554评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,569评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,316评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,766评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,077评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,240评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,912评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,560评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,176评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,425评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,114评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,114评论 2 352