为什么片段着色器中的这种条件如此缓慢?


19

我已经在WebGL中建立了一些FPS测量代码(基于此SO答案),并且发现我的片段着色器的性能有些奇怪。该代码仅在1024x1024画布上呈现一个四边形(或者说是两个三角形),因此所有魔术都发生在片段着色器中。

考虑一下这个简单的着色器(GLSL;顶点着色器只是一个传递):

// some definitions

void main() {
    float seed = uSeed;
    float x = vPos.x;
    float y = vPos.y;

    float value = 1.0;

    // Nothing to see here...

    gl_FragColor = vec4(value, value, value, 1.0);
}

因此,这只是渲染白色画布。在我的机器上,平均速度约为30 fps。

现在,让我们增加数字运算的难度,并根据与位置相关的噪声的几个八度音程来计算每个片段:

void main() {
    float seed = uSeed;
    float x = vPos.x;
    float y = vPos.y;

    float value = 1.0;

      float noise;
      for ( int j=0; j<10; ++j)
      {
        noise = 0.0;
        for ( int i=4; i>0; i-- )
        {
            float oct = pow(2.0,float(i));
            noise += snoise(vec2(mod(seed,13.0)+x*oct,mod(seed*seed,11.0)+y*oct))/oct*4.0;
        }
      }

      value = noise/2.0+0.5;

    gl_FragColor = vec4(value, value, value, 1.0);
}

如果您想运行上面的代码,我一直在使用的实现snoise

这样会将fps降低到7。

现在,很奇怪的部分...通过将噪声计算包装在以下条件中,让我们仅将每16个片段中的一个作为噪声进行计算,而将其余部分留为白色:

if (int(mod(x*512.0,4.0)) == 0 && int(mod(y*512.0,4.0)) == 0)) {
    // same noise computation
}

您可能希望它速度更快,但是仍然只有7 fps。

再进行一次测试,让我们使用以下条件过滤像素:

if (x > 0.5 && y > 0.5) {
    // same noise computation
}

这样可以提供与以前完全相同的噪点像素数量,但是现在我们可以恢复到近30 fps。

这里发生了什么?两种滤除第16个像素的方式不应该给出完全相同的周期数吗?为什么慢的像素像渲染所有像素一样慢呢?

额外的问题:我该怎么办?如果我确实确实想只用一些昂贵的片段来散布我的画布,是否有任何方法可以解决这种可怕的性能?

(可以肯定的是,通过将第16个像素渲染为黑色而不是白色,我已经确认了实际的模运算根本不影响帧速率。)

Answers:


22

像素被分组为小方块(大小取决于硬件),并在单个SIMD管道中一起计算。(SIMD的数组类型的结构)

该管道(根据供应商有几种不同的名称:扭曲,波前)将按步长对每个像素/片段执行操作。这意味着,如果需要完成一个像素的计算,那么所有像素都将对其进行计算,而不需要结果的像素将把它丢弃。

如果所有片段都通过着色器遵循相同的路径,则其他分支将不会执行。

这意味着,第16个像素的第一种计算方法是最坏情况的分支。

如果您仍想缩小图像尺寸,则只需渲染为较小的纹理,然后进行放大即可。


5
渲染到较小的纹理并进行升采样是一种很好的方法。但是,如果由于某些原因您确实需要写入大纹理的每个第16个像素,则使用对每个第16个像素加上一个调用的计算着色器,再加上图像加载/存储,以将写入分散到渲染目标中,可能是一个不错的选择。
内森·里德
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.