GLSL while循环性能独立于在其内部完成的工作

GLSL while循环性能独立于在其内部完成的工作,glsl,webgl,shader,webgl2,Glsl,Webgl,Shader,Webgl2,我目前正在尝试在片段着色器中实现一个路径跟踪器,它利用一个非常简单的BVH BVH交叉口的代码基于以下理念: bool BVHintersects( Ray ray ) { Object closestObject; vec2 toVisit[100]; // using a stack to keep track which node should be tested against the current ray int stackPointer = 1;

我目前正在尝试在片段着色器中实现一个路径跟踪器,它利用一个非常简单的BVH

BVH交叉口的代码基于以下理念:

bool BVHintersects( Ray ray ) {

    Object closestObject;

    vec2 toVisit[100]; // using a stack to keep track which node should be tested against the current ray
    int stackPointer = 1;
    toVisit[0] = vec2(0.0, 0.0);  // coordinates of the root node in the BVH hierarcy    



    while(stackPointer > 0) {
        stackPointer--;  // pop the BVH node to examine

        if(!leaf) {
            // examine the BVH node and eventually update the stackPointer and toVisit
        }

        if(leaf) {
            // examine the leaf and eventually update the closestObject entry
        }
    }
}
以上代码的问题是,在第二次光反弹时,假设我以这种方式计算光反弹,就会发生一些非常奇怪的事情:

vec3 color  = vec3(0.0);
vec3 normal = vec3(0.0);
// first light bounce
bool intersects = BVHintersect(ro, rd, color, normal);

vec3 lightPos = vec3(5, 15, 0);

// updating ray origin & direction
ro = ro + rd * (t - 0.01);
rd = normalize(lightPos - ro);

// second light bounce used only to calculate shadows
bool shadowIntersects = BVHintersect(ro, rd, color, normal);
对BVHintersect的第二次调用将无限期地运行,因为while循环永远不会退出,但从我对第二次调用所做的许多测试中,我确信stackPointer最终会成功返回到0,事实上,如果我将以下代码放在while循环的正下方:

int iterationsMade = 0;
while(stackPointer > 0) {
    iterationsMade++;
    if(iterationsMade > 100) {
        break;
    }
    // the rest of the loop
    // after the functions ends it also returns "iterationsMade"
变量“IterationMake”总是小于100,while循环不会无限运行,但从性能角度看,这就像我进行了“100”次迭代,即使“IterationMake”从来都不大于10或20。将硬编码的“100”增加到更大的值将线性降低性能

这种行为的可能原因是什么?如果第二次调用BVHIntersect的迭代次数从未超过10-20次,那么它陷入while循环的可能原因是什么

BVHintersect函数的源代码:
因此,着色器(或大多数SIMD环境)中的循环有一个有趣的地方:

整个wave的执行时间至少与最慢的线程一样长。因此,如果一个线程需要大约100次迭代,那么它们都需要100次迭代。根据您的平台和编译器,循环可以展开到100次迭代(或您选择的任何上限)。
中断后的任何操作都不会影响最终输出,但展开循环的其余部分仍需处理。早出去并不总是可能的

有很多方法可以解决这个问题,但最简单的方法可能是在多个过程中使用较低的“最大迭代次数”(max iterations)值来实现这一点

我还将通过编译器运行着色器并查看生成的代码。比较具有不同最大迭代次数的不同版本,并查看编译着色器的长度


感谢您的回答3Dave,我立即想到了循环展开,但有趣的是,将“100”改为“10000”不会影响第一次反弹计算,只会影响第二次反弹计算。你认为这可以完全排除循环展开是这个问题的可能原因吗?@Domenico这是一个有趣的观点。我很久以前就不再尝试猜测着色器编译器在做什么了。当你说“第二次反弹”时,你是指
intersectsChild2
路径吗?对不起,我不清楚“第二次反弹”的含义。我指的是BVHintersect的第二个调用,它用于简单地计算阴影光线,如果我注释掉
bool shadowIntersects=BVHintersect(ro,rd,color,normal),它将更加具体我可以将任何硬编码的值替换为“100”,而不会看到性能的任何下降。这是分支执行与意外参数(因为如果wave中的另一个线程通过测试,则始终执行分支)以及检测BVH遍历中进行了多少次迭代的错误组合,谢谢大家的帮助堆栈大小为100是相当大的,没有好的BVH会需要一个这么大的,除非在一些绝对最坏的情况下,所以你可能应该减少到一些更合理的性能,如16。您可能只是在某个地方遇到了一个错误,如果它从未停止使用原始代码,则可能会导致它不正确地遍历树。@LemonDrop while循环的迭代次数从未超过10-20次,“IterationsMake”从未超过任何计算像素的迭代次数,感谢您对堆栈大小的建议,我一直在使用一些任意高的数字只是为了测试,但我会确保缩小这个数字。你的描述没有任何意义,如果while循环在10-20次迭代后退出,它将不会永远继续。对我来说,这听起来像是在100次迭代后才退出循环,这就是为什么增加该值会改变性能。您确定正确读取了IterationMake值吗?我在函数声明中使用了一个inout变量,该变量传递回片段着色器中的主线程,如果IterationMake大于一个设置常量,我只需为该像素输出一个红色。迭代后不会计算红色像素。同样,正如另一条注释中所指定的,硬编码值“100”完全不会影响BVHintersects的第一次调用,在第一次反弹中,它可能高达“10000”,而不会降低性能(仍然计算正确的结果)。我不知道,这可能是很多事情,对我来说,这听起来像是有一个错误的地方,因为结果不工作。可能是主光线也朝向一个很好的方向,但次光线在反弹时会逃逸BVH进入天空,或者是导致循环永远不会结束的东西,只是没有足够的信息来说明。