奇怪的性能下降,由单个for循环引起

时间:2013-10-17 17:35:08

标签: glsl

我目前正在linux上编写一个OpenGL 3.1(带有GLSL版本330)应用程序(NVIDIA 360M卡,带有313.0 nv驱动程序),大约有15k行。我的问题是,在我的一个顶点着色器中,我可以通过在代码中进行实际上无操作的最小变化来体验彻底的性能下降。

例如:

// With this solution my program runs with 3-5 fps
for(int i = 0; i < 4; ++i) {
  vout.shadowCoord[i] = uShadowCP[i] * w_pos;
}

// But with this it runs with 30+ fps
vout.shadowCoord[0] = uShadowCP[0] * w_pos;
vout.shadowCoord[1] = uShadowCP[1] * w_pos;
vout.shadowCoord[2] = uShadowCP[2] * w_pos;
vout.shadowCoord[3] = uShadowCP[3] * w_pos;

// This works with 30+ fps too
vec4 shadowCoords[4];
for(int i = 0; i < 4; ++i) {
  shadowCoords[i] = uShadowCP[i] * w_pos;
}
for(int i = 0; i < 4; ++i) {
  vout.shadowCoord[i] = shadowCoords[i];
}

或者考虑一下:

uniform int uNumUsedShadowMaps = 4; // edit: I called this "random_uniform" in the original question

// 8 fps
for(int i = 0; i < min(uNumUsedShadowMaps, 4); ++i) {
    vout.shadowCoord[i] = vec4(1.0);
}

// 30+ fps
for(int i = 0; i < 4; ++i) {
  if(i < uNumUsedShadowMaps) {
    vout.shadowCoord[i] = vec4(1.0);
  } else {
    vout.shadowCoord[i] = vec4(0.0);
  }
}

在此处查看整个着色器代码,此问题出现在: http://pastebin.com/LK5CNJPD

任何想法都会被赞赏,关于什么可能导致这些。

2 个答案:

答案 0 :(得分:2)

我终于设法找到了问题的根源,并找到了解决方案。

但在开始寻找解决方案之前,请让我粘贴最小的着色器代码,我可以重现这个'bug'。

顶点着色器:

#version 330 

vec3 CountPosition(); // Irrelevant how it is implemented.

uniform mat4 uProjectionMatrix, uCameraMatrix;

out VertexData {
    vec3 c_pos, w_pos;
    vec4 shadowCoord[4];
} vout;

void main() {
    vout.w_pos = CountPosition();
    vout.c_pos = (uCameraMatrix * vec4(vout.w_pos, 1.0)).xyz;
    vec4 w_pos = vec4(vout.w_pos, 1.0);

    // 20 fps
    for(int i = 0; i < 4; ++i) {
        vout.shadowCoord[i] = uShadowCP[i] * w_pos;
    }

    // 50 fps
    vout.shadowCoord[0] = uShadowCP[0] * w_pos;
    vout.shadowCoord[1] = uShadowCP[1] * w_pos;
    vout.shadowCoord[2] = uShadowCP[2] * w_pos;
    vout.shadowCoord[3] = uShadowCP[3] * w_pos;

    gl_Position = uProjectionMatrix * vec4(vout.c_pos, 1.0);
}

Fragment Shader:

#version 330

in VertexData {
    vec3 c_pos, w_pos;
    vec4 shadowCoord[4];
} vin;

out vec4 frag_color;

void main() {
    frag_color = vec4(1.0);
}

有趣的是,只需要对顶点着色器进行最小修改就可以使这两种解决方案都能以50 fps的速度运行。主要功能应该修改为:

void main() {
    vec4 w_pos = vec4(CountPosition(), 1.0);
    vec4 c_pos = uCameraMatrix * w_pos;

    vout.w_pos = vec3(w_pos);
    vout.c_pos = vec3(c_pos);

    // 50 fps
    for(int i = 0; i < 4; ++i) {
        vout.shadowCoord[i] = uShadowCP[i] * w_pos;
    }

    // 50 fps
    vout.shadowCoord[0] = uShadowCP[0] * w_pos;
    vout.shadowCoord[1] = uShadowCP[1] * w_pos;
    vout.shadowCoord[2] = uShadowCP[2] * w_pos;
    vout.shadowCoord[3] = uShadowCP[3] * w_pos;

    gl_Position = uProjectionMatrix * c_pos;
}

有什么不同之处在于上面的代码从着色器中读取变量,而底部的代码将这些值保存在临时变量中,并且仅写入外部变量。

结论:

读取着色器的变化通常被认为是用于减少一个临时变量的优化,或者至少我已经在互联网上的许多地方看到它。尽管存在上述事实,但读取变量实际上可能是无效的OpenGL操作,并且可能使GL进入未定义状态,其中代码中的随机更改可以触发错误的事物。

关于这一点最好的事情是,GLSL 330 specification并没有说任何关于从以前写入的变化中读取的内容。可能是因为这不是我应该做的事情。


P.S。

另请注意,原始代码中的第二个示例可能看起来完全不同,但它在这个小代码片段中的工作方式完全相同,如果读取了外部变化,则i < min(uNumUsedShadowMaps, 4)作为条件会变得很慢for循环,但是如果只写出输出变化,它不会对性能进行任何改变,而i < min(uNumUsedShadowMaps, 4)也可以使用50 fps。

答案 1 :(得分:0)

你得到这样的下降,因为你为循环的每次迭代运行min(random_uniform,4)。通常情况下,这在CPU上无关紧要,但是你在gpu上运行这个程序,它正在为每个顶点运行。因此,即使在4个元素的数组上运行min通常是一个便宜的计算,当你为每个顶点调用它4次时,它会很快加起来。

考虑这样做:

 int check = min(random_uniform,4);
 for(int i = 0; i < check; ++i) {
     vout.shadowCoord[i] = vec4(1.0);
 }

这将为每个顶点运行min(random_uniform,4)一次,而不是4次。

另一个问题可能是random_uniform。这是怎么创造的?找到分钟有什么意义?