计算着色器
简介
计算着色器的操作与其他光栅化的着色器截然不同,其他着色器大多又明确的输入输出数据,也有一些是用户自定义的变量,而计算着色器的工作方式与这些截然不同,计算着色器的计算空间单元很大程度上是抽象的,由用户自定义大小,而计算着色器的计算次数也由执行计算操作的函数定义,而计算着色器没有特定的输入输出数据的描述,只是由用户定义输入输出数据的位置,计算着色器可以处理计算任务或者是处理图像任务
输入输出变量类型
- Storage Buffer:以缓冲的形式存储数据
- Storage Texel Buffer:以缓冲的形式存储数据,以图像的形式访问数据
- Storage Image:以图像的形式存储数据,其子资源图像布局必须为
VK_IMAGE_LAYOUT_SHARED_PRESENT_KHR
或VK_IMAGE_LAYOUT_GENERAL
示例
处理计算任务
computer shader:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
layout(local_size_x = 32, local_size_y = 1, local_size_z = 1) in;
layout(set = 0, binding = 0) buffer Src0 { float src0[];};
layout(set = 0, binding = 1) buffer Src1 { float src1[];};
layout(set = 0, binding = 2) buffer Dst { float dst[];};
void main() {
uint idx = gl_GlobalInvocationID.x;
while (idx >= src0.length()) {
idx = idx - 32;
}
float tmp = src0[idx] + src1[idx];
dst[idx] = dst[idx] + 0.2;
}
这段computer shader代码定义了一个workgroup,它的大小为x=32 y=1 z=1,它的总计算单元就为32*1*1=32,输入数据为src0和src1,输出的数据的dst buffergl_GlobalInvocationID
: 是中在计算着色器(Compute Shader)中使用的一个内置变量,用于标识当前工作组中每个工作项的唯一全局 ID。它是计算着色器中获取每个线程位置信息的重要工具。gl_WorkGroupID
: 当前工作组在整个计算网格中的位置索引。
输入数据为src0和src1都为128个全为0的float数组,输出的数据存在dst buffer中数据为:1
2
3
4dst_data:
[
0.8, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0
]
所以这个32计算单元的工作组就需要执行四次,他的gl_NumWorkGroups xyz相乘就为4也就是在我们执行dispatch的时候输入工作组的xyz参数相乘为41
vkCmdDispatch(commandBuffer, 4, 1, 1);
处理图形任务
computer shader:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
layout(local_size_x = 16, local_size_y = 16, local_size_z = 1) in;
layout(set = 0, binding = 0, rgba8) readonly uniform imageBuffer src_buffer;
layout(set = 0, binding = 1, rgba8) writeonly uniform image2DArray outputImage;
layout(push_constant) uniform ImageInfo {
ivec4 image_offset[2];
int buffer_row_stride;
int buffer_layer_stride;
int layer_counts;
} info;
void main(void)
{
ivec3 rela_pos = ivec3(gl_GlobalInvocationID.xyz);
ivec2 dst_distance = info.image_offset[1].xy - info.image_offset[0].xy;
if (all(lessThan(rela_pos.xy, abs(dst_distance))) && rela_pos.z < info.layer_counts)
{
ivec2 pos = rela_pos.xy + info.image_offset[0].xy;
int buffer_pos = rela_pos.x + rela_pos.y * info.buffer_row_stride + rela_pos.z * info.buffer_layer_stride;
vec4 pixel = imageLoad(src_buffer, buffer_pos);
imageStore(outputImage, ivec3(pos, rela_pos.z), pixel);
}
}
这个shader处理的是一个将buffer内容转化为一张图片的任务
定义输入buffer为imageBuffer,是只读属性,存储的为像素数据,为1D缓冲区
定义输出buffer为outputImage,是只能写入属性,存储的为图像信息,为2D图像信息
ImageInfo中的参数为需要转化的像素在在imageBuffer的片段偏移
buffer_row_stride为转化数据一个像素的大小,这根据像素的格式来决定,buffer_layer_stride指定了图片array_layer之间的偏移,layer_counts指定了图片array_layer的数量
接下来的过程就是逐像素将imageBuffer中的内容拷贝到image2DArray中,这里面computer shader的代码与外部的资源类型相互绑定对应,在调用vkCmdDispatch执行这段shader之后,输出的资源就是图片信息,就能直接使用对应的图像了1
vkCmdDispatch(commandBuffer, image_width/16, image_height/16, 1);