up 指的是设定的处理单元,这个值要根据具体的设备进行区别,但必须是足够小的,能让GPU执行; threadgroupCount 是需要处理的次数,一般来说threadgroupCount*threadgroup=需要处理的大小。
性能相关
临时对象(创建和销毁是廉价的,它们的创建方法都返回 autoreleased对象) 1.Command Buffers 2.Command Encoders 代码中不需要持有。
高消耗对象(在性能相关的代码里应该尽量重用它,避免反复创建) 1.Command Queues 2.Buffers 3.Textures 5.Compute States 6.Render Pipeline States 代码中需长期持有。
Metal常用的四种数据类型:half、float、short(ushort)、int(uint)。 GPU的寄存器是16位,half是性能消耗最低的数据类型;float需要两次读取、消耗两倍的寄存器空间、两倍的带宽、两倍的电量。 为了提升性能,half和float之间的转换由硬件来完成,不占用任何开销。 同时,Metal自带的函数都是经过优化的。 在float和half数据类型混合的计算中,为了保持精度会自动将half转成float来处理,所以如果想用half节省开销的话,要避免和float混用。 Metal同样不擅长处理control flow,应该尽可能使用使用三元表达式,取代简单的if判断。
此部分参考自WWDC
常见的图形渲染管道
Metal Shader Language的使用场景有两个,分别是图形渲染和通用计算;基于C++ 14,运行在GPU上,GPU的特点:带宽大,并行处理,内存小,对条件语句处理较慢(等待时间长)。 Metal着色语言使用clang和 LLVM,支持重载函数,但不支持图形渲染和通用计算入口函数的重载、递归函数调用、new和delete操作符、虚函数、异常处理、函数指针等,也不能用C++ 11的标准库。
基本函数
shader有三个基本函数:
- 顶点函数(vertex),对每个顶点进行处理,生成数据并输出到绘制管线;
- 像素函数(fragment),对光栅化后的每个像素点进行处理,生成数据并输出到绘制管线;
- 通用计算函数(kernel),是并行计算的函数,其返回值类型必须为void;
顶点函数相关的修饰符:
- [[vertex_id]] vertex_id是顶点shader每次处理的index,用于定位当前的顶点
- [[instance_id]] instance_id是单个实例多次渲染时,用于表明当前索引;
- [[clip_distance]],float 或者 float[n], n必须是编译时常量;
- [[point_size]],float;
- [[position]],float4;
如果一个顶点函数的返回值不是void,那么返回值必须包含顶点位置; 如果返回值是float4,默认表示位置,可以不带[[ position ]]修饰符; 如果一个顶点函数的返回值是结构体,那么结构体必须包含“[[ position ]]”修饰的变量。
像素函数相关的修饰符:
- [[color(m)]] float或half等,m必须是编译时常量,表示输入值从一个颜色attachment中读取,m用于指定从哪个颜色attachment中读取;
- [[front_facing]] bool,如果像素所属片元是正面则为true;
- [[point_coord]] float2,表示点图元的位置,取值范围是0.0到1.0;
- [[position]] float4,表示像素对应的窗口相对坐标(x, y, z, 1/w);
- [[sample_id]] uint,The sample number of the sample currently being processed.
- [[sample_mask]] uint,The set of samples covered by the primitive generating the fragmentduring multisample rasterization.
以上都是输入相关的描述符。像素函数的返回值是单个像素的输出,包括一个或是多个渲染结果颜色值,一个深度值,还有一个sample遮罩,对应的输出描述符是[[color(m)]] floatn、[[depth(depth_qualifier)]] float、[[sample_mask]] uint。
struct LYFragmentOutput {
// color attachment 0
float4 color_float [[color(0)]];// color attachment 1
int4 color_int4 [[color(1)]];// color attachment 2
uint4 color_uint4 [[color(2)]];};
fragment LYFragmentOutput fragment_shader( ... ) { ... };
需要注意,颜色attachment的参数设置要和像素函数的输入和输出的数据类型匹配。
Metal支持一个功能,叫做前置深度测试(early depth testing),允许在像素着色器运行之前运行深度测试。如果一个像素被覆盖,则会放弃渲染。使用方式是在fragment关键字前面加上[[early_fragment_tests]]: [[early_fragment_tests]] fragment float4 samplingShader(..)
使用前置深度测试的要求是不能在fragment shader对深度进行写操作。 深度测试还不熟悉的,可以看LearnOpenGL关于深度测试的介绍。
参数的地址空间选择
Metal种的内存访问主要有两种方式:Device模式和Constant模式,由代码中显式指定。 Device模式是比较通用的访问模式,使用限制比较少,而Constant模式是为了多次读取而设计的快速访问只读模式,通过Constant内存模式访问的参数的数据的字节数量是固定的,特点总结为:
- Device支持读写,并且没有size的限制;
- Constant是只读,并且限定大小;
如何选择Device和Constant模式? 先看数据size是否会变化,再看访问的频率高低,只有那些固定size且经常访问的部分适合使用constant模式,其他的均用Device。
// Metal关键函数用到的指针参数要用地址空间修饰符(device, threadgroup, or constant) 如下
vertex RasterizerData // 返回给片元着色器的结构体
vertexShader(uint vertexID [[ vertex_id ]], // vertex_id是顶点shader每次处理的index,用于定位当前的顶点
constant LYVertex *vertexArray [[ buffer(0) ]]); // buffer表明是缓存数据,0是索引
地址空间的修饰符共有四个,device、threadgroup、constant、thread。 顶点函数(vertex)、像素函数(fragment)、通用计算函数(