Metal Metal Shader Language

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