设为首页 加入收藏

TOP

《OpenGL编程指南(原书第8版)》——计算着色器(二)
2015-08-31 21:23:20 来源: 作者: 【 】 浏览:53
Tags:OpenGL 编程 指南 计算 着色
用缓存对象中存储的参数。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 计算着色器中的内置变量声明



这些计算着色器的定义如下:


?


?


?


假设已经知道自己在本地工作组和全局工作组中的位置,则可以利用信息来操作数据。如例12.5所示,加入一个图像变量使得我们能够将数据写入由当前执行单元坐标决定的图像位置中去,并且可以在计算着色器中更新。


例12.5 数据的操作



例12.5中的着色器把执行单元在本地工作组中的坐标按本地工作组大小进行归一化, 然后将该结果写入由全局请求ID确定的图像位置上去。 图像结果表达了全局和本地的请求ID的关系,并且展示在计算着色器中定义的矩形的工作组。(本例有32*16个执行单元,图像如12.2所示)


为了生成如图12-2的图像, 在计算着色器写完数据后,只需简单地将纹理渲染至一个全屏的三角条带上即可。


当调用glDispatchCompute()(或者glDispatchComputeIndirect())的时候,图形处理器的内部将执行大量的工作。图形处理器会尽可能采取并行的工作方式,并且每个计算着色器的请求都被看作是一个执行某项任务的小队。我们必然要通过通信来加强团队之间的合作,所以即使OpenGL并没有定义执行顺序和并行等级的信息,我们还是可以在请求之间建立某种程度的合作关系,以实现变量的共享。此外,我们还可以对一个本地工作组的所有请求进行同步,让它们在同一时刻同时抵达着色器的某个位置。



图12-2 全局和本地的请求ID的关系


?


?


我们可以使用shared关键字来声明着色器中的变量,其格式与其它的关键字,例如uniform、in、out等类似。例12.6给出了一个使用shared关键字来进行声明的示例。


例12.6 声明共享变量的示例



如果一个变量被声明为shared,那么它将被保存到特定的位置,从而对同一个本地工作组内的所有计算着色器请求可见。如果某个计算着色器请求对共享变量进行写入,那么这个数据的修改信息将最终通知给同一个本地工作组的所有着色器请求。在这里我们用了“最终”这个词,这是因为各个着色器请求的执行顺序并没有定义,就算是同一个本地工作组内也是如此。因此,某个着色器请求写入共享shared变量的时刻可能与另一个请求读取该变量的时刻相隔甚远,无论先写入后读取还是先读取后写入。为了确保能够获得期望的结果,我们需要在代码中使用某种同步的方法。下一个小节详细介绍这一问题。


?


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


因为需要把声明为shared的变量存储到图形处理器的高性能资源环境中,而这样的资源环境是有限的,所以需要查询和了解某个计算着色器程序的共享变量的最大数量。要获取这个限制值,可以调用glGetIntegerv()并设置pname为GL_MAX_COMPUTE_SHARED_MEMORY_SIZE。


?


?


如果本地工作组请求的执行顺序,以及全局工作组中的所有本地工作组的执行顺序都没有定义,那么请求执行操作的时机与其他请求就是完全无关的。如果请求之间不需要互相通信,只需完全独立地执行,那么这样并没有什么问题。但是,如果请求之间需要进行通信,无论是通过图像,缓存还是共享内存,那么我们就有必要对它们的操作进行同步处理了。


同步命令的类型有两种。首先是运行屏障(execution barrier),可以通过barrier()函数触发。它与细分控制着色器中的barrier()函数类似,后者可以用来实现控制点处理过程中的请求同步。如果计算着色器的一个请求遇到了barrier(),那么它会停止运行,并等待同一个本地工作组的所有请求到达为止。当请求从barrier()中断的地方重新开始运行的时候,我们可以断定其它所有的请求也已经到达了barrier(),并且在此之前的所有操作均已经完成。barrier()函数在计算着色器中的用法比在细分控制着色器中更为灵活。尤其是,不需要限制在着色器中的main()函数中执行barrier()。但是,必须在统一的流控制过程中调用barrier()。也就是说,如果本地工作组的一个请求执行了barrier()函数,那么同一工作组的所有请求都必须执行这个函数。这样是合理的,因为着色器的某个请求不可能知道其它请求的控制流情况,所以只能假设其它请求也能到达屏障的位置,否则将会发生死锁的情形。


如果在本地工作组内进行请求间的通信,那么可以在一个请求中写入共享变量,然后在另一个请求中读取。但是,我们必须确定目标请求中读取共享变量的时机,即在源请求已经完成对应的写入操作之后。为了确保这一点,我们可以在源请求中写入变量,然后在两个请求中同时执行barrier()函数

首页 上一页 1 2 3 下一页 尾页 2/3/3
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇快速排序的简单实现 下一篇C语言宏定义#define用法

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容: