OpenGL中readInvocationARB读取非活跃调用返回结果不一致问题
编写着色器程序时,我需要对多组不同输入重复执行相同算法计算中间结果,再合并中间结果得到最终输出。由于所用计算数据与游戏内世界位置绑定,同一子组内各调用的数据差异极小,因此我采用了并行方案:在不同调用上同时处理所有输入,再通过子组内调用间的数据传递完成中间结果合并,得到最终值。
该方案的难点在于:部分分支下中间结果计算完成后可提前退出流程,这就导致部分调用依赖其他调用的计算结果,但被依赖的调用在跨调用传递数据的节点可能已经处于非活跃状态。
OpenGL提供ARB_shader_ballot扩展,支持开发者读取子组内其他调用的变量值,核心接口为readInvocationARB(genUType value, uint invocationIndex)。该接口的公开说明仅提到两点:
readInvocationARB会向子组内所有活跃调用返回指定invocationIndex对应调用传入的<value>- 所有活跃调用传入的
invocationIndex参数必须保持一致,否则结果未定义
但规范并未明确说明:当被读取的目标调用处于非活跃状态时,返回的<value>是否有明确定义。
为验证该行为,我编写了第一版测试着色器:
第一版测试着色器
#version 460 #extension GL_ARB_shader_ballot : enable uniform uint data; layout(location = 0) out vec4 color; void main() { uint var; var = data; if(gl_SubGroupInvocationARB != 20) { const uint result = readInvocationARB(var, 20); color = vec4(result == data); } else color = vec4(1, 0, 0, 1); }
该着色器运行后输出带红色点的白色图像,说明此时读取到的、处于非活跃状态的20号调用的var值为此前写入的data值。
第二版测试着色器
随后我修改变量赋值逻辑,增加条件分支,编写了第二版测试着色器:
#version 460 #extension GL_ARB_shader_ballot : enable uniform uint threshold; uniform uint data; layout(location = 0) out vec4 color; void main() { uint var; if(gl_SubGroupInvocationARB < threshold) var = data; else var = 10; if(gl_SubGroupInvocationARB != 20) { const uint result = readInvocationARB(var, 20); color = vec4(result == data); } else color = vec4(1, 0, 0, 1); return; }
测试时我将data uniform的值设为10,保证所有分支下var的实际赋值恒为10,且threshold的取值不会改变var的实际值。但在搭载Nvidia显卡的设备上运行该着色器时,输出为带红色点的黑色图像,说明读取到的20号调用的var值并非此前写入的数值(实测返回值为0)。后续我在搭载AMD显卡的设备上运行上述两个版本的着色器,输出均为带红色点的白色图像,与Nvidia设备上第二版着色器的运行结果存在明显差异。上述非严谨测试表明,不同软硬件配置下,相同着色器的运行结果不一致。
现就此问题提出问询:
- 是否我遗漏了规范相关说明——
readInvocationARB读取非活跃调用的返回结果本身属于未定义行为?如果是,哪里可以获取该扩展的完整权威规范说明? - 若该场景下的返回结果是明确定义的,是我的测试代码存在错误,还是某一厂商的驱动实现不符合规范?
内容的提问来源于stack exchange,提问作者Arkadiy Mel

