第十二章 计算着色器

第十二章 计算着色器

工作组及其执行

计算着色器中任务以组为单位进行执行,称为工作组。拥有邻居的工作组称为本地工作组,这些组可以组成更大的组,称为全局工作组。计算着色器会被全局工作组的每一个单元调用一次。本地工作组和全局工作组都是3维的,如果不需要某个维度可以将其大小设置为0(感觉应该是1)。

局部工作组的大小在着色器中使用布局限定符声明:

#version 430 core
layout(local_size_x = 16, local_size_y = 16) in;

void main()
{
    // 什么都不做
}

在OpenGL中创建并链接一个计算着色器之后,就可以通过glUseProgram()函数将其设置为当前要执行的程序,然后用glDispatchCompute()将工作组发送到工作管线上

void glDispatchCompute(GLuint num_group_x, GLuint num_group_y, GLuint num_group_z);

三个参数分别表示工作组的维度,每个参数都必须是正数且小于等于设备相关常量数组GL_MAP_COMPUTE_WORK_GROUP_SIZE对应的元素。
也可以使用GL_DISPATCH_INDIRECT_BUFFER中保存的值:

void glDispatchComputeIndirect(GLintptr indirect);

indirect表示缓存中存储参数位置的偏移,缓存中当前偏移量位置的参数是紧密排列的三个无符号整数值,分别为工作组的三个维度。

如果需要在OpenGL中获取本地工作组的大小可以使用glGetProgramiv(),并将pname设置为GL_MAX_COMPUTE_WORK_GROUP_SIZE,param设置成包含三个无符号整数数组的地址。

获取工作组的位置

需要使用的计算着色器的内置变量

const uvec3 gl_WorkGroupSize; // 保存本地工作组大小
in    uvec3 gl_NumWorkGroups; // 保存全局工作组的大小
in    uvec3 gl_LocalInvocationID; // 当前执行单元在本地工作组中的位置
in    uvec3 gl_WorkGroupID; // 当前本地工作组在全局工作组中的位置
in    uvec3 gl_GlobalInvocationID; // gl_WorkGroupID * gl_WorkGroupSize + gl_LocalInvocationID,当前执行单位在全局工作中的位置
in    uint  gl_LocalInvocationIndex; // gl_LocalInvocationID.z * gl_WorkGroupSize.x * gl_WorkGroupSize.y + gl_LocalInvocationID.y * gl_WorkGroupSize.x + gl_LocalInvocationID.x

通信与同步

通信

可以使用shared变量来声明着色器中的变量,如果变量被声明为shared,那么它将被保存到特定的位置,从而被同一个本地工作组内的所有计算着色器请求可见。如果某个计算着色器请求对共享变量进行了写入,那么这个数据的修改信息将最终通知给同一个本地工作组的所有着色器请求。

通常访问共享shared变量的性能会远远好于访问图像或者着色器存储缓存的性能。因为着色器处理器会将共享内存作为局部变量处理,并且可以在设备中进行拷贝,所以访问共享变量可能比使用缓冲区的方法更加迅速,如果着色器需要对移除内存进行大量的拷贝,尤其可能是需要多个着色器请求访问同一处内存地址的时候,可以先将内存拷贝到着色器的共享变量中,然后通过这种方法进行操作,如果有必要,再将结果写回到主内存中。

同步

同步命令分为两种,首先是运行屏障(execution barrier),可以通过barrier()函数触发,如果着色器的一个请求遇到barrier()那么它会停止运行,并等待同一个本地工作组的所有请求都到达为止,并且不限制只能在main函数中调用,但是要求只有有一个请求执行了barrier()函数其它所有请求都必须执行,否则会死锁。

第二种是内存屏障,通过调用memoryBarrier(),如果调用那么就可以保证着色器请求写入内存的操作一定是提交到了内存端,而不是通过缓冲区或者是调度队列之类的方法。所有发生在调用之后的操作在读取同一处内存时,都可以使用这些内存写入的结果,即使同一个计算着色器的其它请求也是如此。此外memoryBarrier()也可以对着色器编译器做出指示,让它不要对内存操作重新排序,以免跨越屏障函数。

memoryBarrierAtomicCounter()会等待原子计数器的更新,然后继续执行。memoryBarrierBuffer()和memoryBarrierImage()函数会等待缓存和图像变量的写入操作完成。memoryBarrierShared()会等待带有shared限定符的变量更新。groupMemoryBarrier()等价于memoryBarrier(),但是只能应用于同一本地工作组的其它请求,而其他的函数都是作用域全局范围的。

就算是调用memoryBarrier()或者是某个子函数,也不能保证所有请求都达到着色器的同一位置,只有先调用barrier(),然后在读取内存数据,而后者应该是在memoryBarrier()之前写入的。

内存屏障在同一个着色器请求中是不必要的,某个请求中读取变量的值总是会返回总后一次写入这个变量的结果。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值