尽管例子12.1中的着色器什么事情也没做,它仍然是一个“完整”的着色器,可以正常的编译、链接并且在OpenGL硬件中执行。要创建一个计算着色器,只需调用glCreateShader ()函数,将类型设置为GL_COMPUTE_SHADER,并且调用glShaderSource()函数来设置着色器的源代码, 接着就能按正常编译了。然后把着色器附加到一个程序上,调用glLinkProgram()。这样就会产生计算着色器阶段需要的可执行程序。例12.2展示了从创建到链接一个计算程序(使用“计算程序”来表示使用计算着色器来编译的程序)的完整步骤。
例12.2 创建,编译和链接计算着色器
一旦像例12.2中那样创建并链接一个计算着色器后,就可以用glUseProgram()函数把它设置为当前要执行的程序,然后用glDispatchCompute()把工作组发送到计算管线上,其原型如下:
Void glDispatchCompute(GLuint num_groups_x, GLuint num_groups_y, GLuint num_groups_z);
在3个维度上分发计算工作组。num_groups_x,num_groups_y和num_groups_z分别设置工作组在X,Y和Z维度上的数量。每个参数都必须大于0,小于或等于一个与设备相关的常量数组GL_MAX_COMPUTE_WORK_GROUP_SIZE的对应元素。
在调用glDispatchCompute()时,OpenGL会创建一个包含大小为num_groups_x * num_groups_y * num_gourps_z的本地工作组的3维数组。注意三个维度中一个或两个维度可以为1或者glDispatchCompute()的参数的任何值。所以计算着色器中执行单元的总数是这个3维数组的大小乘以着色器代码中定义的本地工作组的大小。可想而知,这种方法可以为图像处理器创建非常大规模的工作负载,而通过计算着色器则可以相对容易地获得并行性。
正如glDrawArraysIndirect()和glDrawArrays()的关系一样,除了使用glDispatchCompute()之外通过glDispatchComputeIndirect()可以使用存储在缓冲区对象上的参数来发送计算任务。缓冲区对象被绑定在GL_DISPATCH_INDIRECT_BUFFER上,并且缓冲区中存储的参数包含三个打包在一起的无符号整数。这三个无符号整数的作用和glDispatchCompute()中的参数是等价的。参考glDispatchComputeIndirect的原型如下:
void glDispatchComputeIndirect(GLintptr indirect);
在三个维度上分发计算工作组,同时使用缓存对象中存储的参数。indirect表示缓存数据中存储参数的位置偏移量,使用基本机器单位。缓存中当前偏移位置的参数,是紧密排列的三个无符号整数值,用来表示本地工作组的数量。这些无符号整数值等价于glDispatchCompute()中的num_groups_x,num_groups_y和num_groups_z参数。每个参数都必须大于0,小于或等于一个设备相关的常量数组GL_MAX_COMPUTE_WORK_GROUP_SIZE的对应元素。
绑定在GL_DISPATCH_INDIRECT_BUFFER上的缓冲区数据的来源可以多种多样,比如由另外一个计算着色器生成。这样一来,图形处理器就能够通过设置缓冲区中的参数来给自身发送任务做计算或绘图。例12.3中使用glDispatchComputeIndirect()来发送计算任务。
例12.3 分发计算工作量
注意到例12.3简单地使用glUseProgram()把当前的程序对象指向某个特定计算程序。除了不能访问图形管线中的那些固定功能部分(如光栅器或帧缓存),计算着色器及其程序是完全正常的,这意味着你可以用glGetProgramiv()来请求它们的一些属性(比如有效的uniform常量,或者存储块)或者像往常一样访问uniform常量。当然,计算着色器可以访问所有其他着色器能访问的资源,比如图像,采样器,缓冲区,原子计数器,以及常量存储块。
计算着色器及其程序还有一些独有的属性。比如,获得本地工作组的大小(在源代码的布局限定符中设置),调用glGetProgramiv()时将pname设置成GL_MAX_COMPUTE_WORK_GROUP_SIZE以及把param设置成包含三个无符号整型数的数组地址。这数组中的三个数会按顺序被赋值为本地工作组在X,Y和Z方向上的大小。
知道工作组的位置一旦开始执行计算着色器,它就有可能需要对输出数组的一个或多个单元赋值(比如一副图像或者一个原子计数器数组),或者需要从一个输入数组的特定位置读取数据。为此得知道当前处于本地工作组中的什么位置,以及在更大范围的全局工作组中的位置。于是,OpenGL为计算着色器提供一组内置变量。如例12.4所示,这些内置变量被隐含地声明。
例12.4 计算着色器中的内置变量声明
这些计算着色器的定义如下: