简介

计算着色器的操作与其他光栅化的着色器截然不同,其他着色器大多又明确的输入输出数据,也有一些是用户自定义的变量,而计算着色器的工作方式与这些截然不同,计算着色器的计算空间单元很大程度上是抽象的,由用户自定义大小,而计算着色器的计算次数也由执行计算操作的函数定义,而计算着色器没有特定的输入输出数据的描述,只是由用户定义输入输出数据的位置,计算着色器可以处理计算任务或者是处理图像任务


输入输出变量类型

  • Storage Buffer:以缓冲的形式存储数据
  • Storage Texel Buffer:以缓冲的形式存储数据,以图像的形式访问数据
  • Storage Image:以图像的形式存储数据,其子资源图像布局必须为VK_IMAGE_LAYOUT_SHARED_PRESENT_KHRVK_IMAGE_LAYOUT_GENERAL

示例

处理计算任务

computer shader:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#version 450

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 buffer
gl_GlobalInvocationID : 是中在计算着色器(Compute Shader)中使用的一个内置变量,用于标识当前工作组中每个工作项的唯一全局 ID。它是计算着色器中获取每个线程位置信息的重要工具。
gl_WorkGroupID : 当前工作组在整个计算网格中的位置索引。

输入数据为src0和src1都为128个全为0的float数组,输出的数据存在dst buffer中数据为:

1
2
3
4
dst_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参数相乘为4

1
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
#version 450
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);


参考链接

参考链接0
参考链接1
参考链接2
参考链接3
参考链接4