【问题标题】:GLSL shader not unrolling loop when neededGLSL着色器在需要时不展开循环
【发布时间】:2013-09-01 10:54:45
【问题描述】:

我的 9600GT 讨厌我。

片段着色器:

#version 130

uint aa[33] = uint[33](
    0,0,0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,0,0,
    0,0,0
);

void main() {
    int i=0;
    int a=26;

    for (i=0; i<a; i++) aa[i]=aa[i+1];

    gl_FragColor=vec4(1.0,0.0,0.0,1.0);

}

如果a=25 程序以 3000 fps 运行。
如果a=26 程序以 20 fps 运行。
如果aa 视口大小为 1000x1000。
仅当aa 的大小>32 时才会出现问题。
a 的值作为阈值随循环内对数组的调用而变化(aa[i]=aa[i+1]+aa[i-1] 给出不同的截止日期)。
我知道gl_FragColor 已被弃用。但这不是问题。

我的猜测是,如果 a>25 和 size(aa)>32,GLSL 不会自动展开循环。为什么。之所以依赖于数组的大小,是人类不知道的。

此处解释了一个非常相似的行为:
http://www.gamedev.net/topic/519511-glsl-for-loops/

手动展开循环确实可以解决问题 (3000 fps),即使 aa 大小>32:

    aa[0]=aa[1];
    aa[1]=aa[2];
    aa[2]=aa[3];
    aa[3]=aa[4];
    aa[4]=aa[5];
    aa[5]=aa[6];
    aa[6]=aa[7];
    aa[7]=aa[8];
    aa[8]=aa[9];
    aa[9]=aa[10];
    aa[10]=aa[11];
    aa[11]=aa[12];
    aa[12]=aa[13];
    aa[13]=aa[14];
    aa[14]=aa[15];
    aa[15]=aa[16];
    aa[16]=aa[17];
    aa[17]=aa[18];
    aa[18]=aa[19];
    aa[19]=aa[20];
    aa[20]=aa[21];
    aa[21]=aa[22];
    aa[22]=aa[23];
    aa[23]=aa[24];
    aa[24]=aa[25];
    aa[25]=aa[26];
    aa[26]=aa[27];
    aa[27]=aa[28];
    aa[28]=aa[29];
    aa[29]=aa[30];
    aa[30]=aa[31];
    aa[31]=aa[32];
    aa[32]=aa[33];

【问题讨论】:

  • @NicolBolas 为什么当 a=26 帧率急剧下降?
  • 唯一知道这一点的人是实现了您的 OpenGL 编译器的人。
  • 是否有类似 #pragma optionNV(unroll none) 之类的命令强制 opengl 始终执行展开?
  • @user2464424:是的,NV 确实有很多专有的 GLSL #pragma 指令。展开循环是这些指令之一。您想要的特定非便携式编译指示是#pragma optionNV (unroll all)。通常最好自己展开这些东西,因为 AMD/Intel/... 不知道 #pragma 是什么。每个供应商实现自己的编译器的乐趣 - 我有点喜欢 HLSL 的一件事,微软实现了唯一的编译器,所以那里的一切都非常一致。
  • 我预计速度差异主要是由于循环代码在展开后全部被消除(如果未展开则不会被消除),而不是由于循环开销。如果您将循环替换为无法消除死代码或恒定折叠到几乎没有的东西,我希望您不会看到展开代码和非展开代码之间的速度差异很大......

标签: opengl glsl fragment-shader loop-unrolling


【解决方案1】:

我只是在这里总结了 cmets 的答案,因此这不再显示为未回答。

"#pragma optionNV(全部展开)"

修复了 nvidia 上的直接问题。

一般来说,GLSL 编译器非常依赖于实现。恰好在 32 处下降的原因很容易通过点击编译器启发式来解释,例如“不要展开超过 32 的循环”。此外,巨大的速度差异可能来自使用常量的展开循环,而动态循环将需要可寻址的数组内存。另一个原因可能是,在展开死代码消除时,不断折叠会导致整个循环减少到零。

解决这个问题的最便携的方法是手动展开,甚至更好的手动常量折叠。在片段着色器中计算可以在外部计算的常量总是有问题的。有些司机可能会在某些情况下发现它,但最好不要依赖它。

【讨论】:

  • 我要指出循环长度是 26,而不是 32。32 是数组的大小。基本上,这是一个地狱般的极端案例。至少有 3 个相互冲突的因素:1)编译器仅在展开循环时才删除死代码。 2) 常量数组在 32 个浮点数后变成 VRAM 分配的数组。 3) 循环自动展开前的长度阈值取决于循环本身所涉及的变量类型。结论:在这种情况下,任何人都不应该发生,因为我发布的代码是非常糟糕的做法,不应该在任何实际场景中使用。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-11-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-11-30
  • 1970-01-01
相关资源
最近更新 更多