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

我目前正在
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

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

最佳答案 我终于设法找到了问题的根源,并找到了解决方案.

但在进入解决方案之前,请让我粘贴最小的着色器代码,我可以重现这个’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);
}

片段着色器:

#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没有说任何关于从以前写入的变化中读取的内容.可能是因为这不是我应该做的事情.

附:

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

点赞