Metal Shading Language 是做什么用的?
-
Metal着色语言
是用来编写3D图形渲染逻辑和并行计算核心逻辑的一门编程语言。当你使用Metal框架
来完成APP的实现时则需要使用Metal编程语言
-
Metal着色语言
使用Clang
和LLVM
进行编译处理 -
Metal着色语言
基于C++ 11.0 语言设计。我们主要用来编写在GPU上执行的图像渲染逻辑代码以及通用并行计算逻辑代码
Metal 语言中对于指针使用的限制
-
Metal
图形和并行计算函数用到的参数,如果是指针必须使用地址空间修饰符(device,threadgroup,constant) - 不支持函数指针
- 函数名不能出现main
Metal 像素坐标系统
Metal
中纹理/帧缓存区attachment
的像素使用的坐标系统的原点是左上角。
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 | 表示一个空的值的集合 |
纹理 Textures 类型
纹理数据类型
texture1d<T, access a = access::sample>
texture2d<T, access a = access::sample>
texture3d<T, access a = access::sample>
代码示例:
void foo ( texture2d<float> imgA [[ texture(0) ]] ,
texture2d<float, access::read> imgB [[ texture(1) ]],
texture2d<float, access::write> imgC [[ texture(2) ]] ) {
...
}
纹理数据类型是一个句柄,它指向一个一维/二维/三维纹理数据,在一个函数中描述纹理对象的类型。而纹理数据对应这个纹理的某个level的mipmap的全部或一部分
access枚举值:定义了访问权利
enum class access {sample, read, write}
-
sample: 支持采样器(
sampler
), 支持通过采样器访问纹理数据, 也可以不使用采样器访问数据 -
read: 不支持采样器, 并行计算
kernal
函数 或者 图形渲染graphics
函数可以读取纹理数据 -
write: 并行计算
kernal
函数 或者 图形渲染graphics
函数可以将数据写入纹理
T: 数据类型
这里的T表示对纹理数据进行读写操作时的颜色类型, 不含深度信息的纹理数据类型可以为half, float, short, ushort, int
或者uint
, 带深度信息的纹理数据类型必须为float
。
采样器 Samplers
代码示例:
constexpr sampler s(coord::pixel, address::clamp_to_zero, filter::line
ar);
constexpr sampler a(coord::normalized);
constexpr sampler b(address::repeat);
constexpr sampler s(address::clamp_to_zero,filter::linear);
注意:在Metal 程序中初始化的采样器必须使用constexpr修饰符声明
Metal支持的采样器状态和默认值
枚举名称(Enum Name) | 有效值(Valid Value) | 描述(Description) |
---|---|---|
coord | normalized,pixel | 从纹理中采样时,纹理坐标是否需要归一化 |
address | clamp_to_edge, clamp_to_zero, mirrored_repeat, repeat | 设置所有的纹理坐标的寻址方式 |
s_address, t_ address, r_address | clamp_to_edge, clamp_to_zero, mirrored_repeat, reapeat | 设置某个纹理坐标的寻址方式 |
filter | nearest, linear | 设置纹理采样的放大和缩小的过滤方式 |
mag_filter | nearest, linear | 设置纹理采样的放大过滤方式 |
min_filter | nearest, linear | 设置纹理采样的缩小过滤方式 |
map_filter | none, nearest, linear | 纹理采样的minmap过滤方式,如果是none,那么只有一层纹理生效 |
compare_func | none, less,less_qual, greater, greater_equal, equal, not_equal | 为使用r纹理坐标做shadow map设置比较测试逻辑,这个状态值的设置只可以在Metal着色语言程序中完成 |
函数修饰符
Metal
有以下3种函数修饰符
-
kernel ,表示该函数是一个
数据并行计算着色函数
,它将被分配在一个一维/二维/三维的线性组网格中执行。 -
vertex ,表示该函数是一个
顶点着色函数
,它将为顶点数据流中的每个顶点数据执行一次,然后为每个顶点生成数据输出到绘制管线中去。 -
fragment ,表示该函数是一个
片元着色函数
,它将片元数据流中的每个片元和其关联的数据执行一次,然后为每个片元生成数据输出到绘制管线中去。
注意:- 使用kernel修饰的函数,其返回值类型必须是void类型
- 一个被函数修饰符修饰的函数,不能再调用其他也被函数修饰符修饰的函数,这样会导致编译失败
错误示范:
kernel void hello2(...){...}
vertex float4 hello1(...){
// 一个被函数修饰符修饰的函数,不能再调用其他也被函数修饰符修饰的函数,这样会导致编译失败
hello2(...) // 错误调用❌
}
用于变量或参数的地址空间修饰符
Metal着色语言
使用地址空间修饰符来我表示一个函数或参数变量被分配于哪一片内存区域。所有的着色函数(kernel,vertex,fragment)的参数,如果是指针或是引用,都必须带有地址空间修饰符。以下是4种地址空间修饰符:
- device
- threadgroup
- constant
- thread
注意:- 对于图形着色器函数,其指针或是引用类型的参数必须定义为device或是constant地址空间
- 对于并行计算着色器函数,其指针或是引用类型的参数必须定义为device或是threadgroup或是constant地址空间
Device Address Space(设备地址空间)
在device Address Space(设备地址空间)指向设备内存池分配出来的缓存对象,它是可读也是可写的;一个缓存对象可以被声明成一个标量、向量、用户自定义结构体指针或引用。
代码示例:
// 4维向量
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;
// 在线程组地址空间中分配的10个元素的浮点数组
threadgroup float b[10];
}
constant Address Space 常量地址空间
- 常量地址空间指向的缓存对象也是从设备内存池分配存储,但它是只读的
- 在程序域的变量必须定义在常量地址空间并且声明的时候初始化,用来初始化的值必须是编译时的常量
- 在程序域的变量的生命周期和程序一样,在程序中的并行计算着色函数或者图形绘制着色函数调用,但
constant
的值会保持不变
代码示例:
constant float samples[] = { 1.0f, 2.0f, 3.0f, 4.0f };
// 对一个常量地址空间的变量赋值会失败,因为它是只读的
sampler[4] = {3,3,3,3}; // 编译失败
// 定义为常量地址空间声明是不赋初值也会编译失败
constant float a;
注意:常量地址空间的指针或是引用可以作为函数的参数。由代码示例可以看出:
- 向声明为常量的变量赋值会产生编译错误。
- 声明为常量但是没有赋予初值也会产生编译错误。
thread Address Space 线程地址空间
thread 地址空间指向每个线程准备的地址空间,这个线程的地址空间定义的变量在其他线程不可见。主要用于声明的图形绘制着色函数(graphics
函数)或并行计算着色函数(kernal
函数)内部的变量定义。
代码示例:
kernel void my_func(...)
{
float x;
thread float p = &x;
...
}
函数参数与变量
图形绘制或是并行计算着色函数的输入/输出都需要通过参数传递(除了常量地址空间变量和程序域中定义的采样器以外)。参数可以是如下之一:
- device buffer — 设备缓存
- constant buffer — 常量缓存
- texture buffer — 纹理缓存
- sample buffer — 采样器缓存
- threadgroup buffer — 线程共享的缓存
内建变量属性修饰符
用于寻址缓存、纹理、采样器属性修饰符,为每个参数指定一个属性修饰符,指定明确的缓冲、纹理位置。
- device 和 constant buffers — [[ buffer (index) ]]
- texture — [[ texture (index) ]]
- sampler — [[ sampler (index) ]]
- threadgroup buffer — [[ threadgroup (index) ]]
- [[ vertex_id ]] 顶点
id
标识符 - [[ position ]] 顶点信息(float4) // 描述了片元的窗口相对坐标(x, y, z, 1/w)
- [[ point_size ]] 点的大小
- [[ color(m) ]] 颜色,m编译前得确定
- [[ stage_in ]] 片元着色函数使用的单个片元输入数据是由顶点着色函数输出然后经过光栅化生成的。顶点和片元着色函数都是只能有一个参数被声明为使用“
stage_in
”,对于一个使用了stage_in
修饰符的自定义结构体,其成员可以为一个整形或浮点标量,也可以是整形或浮点向量。
代码示例:
struct MyFragmentOutput {
// color attachment 0
float4 clr_f [[color(0)]];
// color attachment 1
int4 clr_i [[color(1)]];
// color attachment 2
uint4 clr_ui [[color(2)]];
};
fragment MyFragmentOutput my_frag_shader( ... )
{
MyFragmentOutput f;
...
f.clr_f = ...;
...
}
网友评论