“字节码”代替硬编码着色器性能

时间:2017-05-23 20:08:24

标签: opengl glsl shader

我正在制作一个生成模型的图形程序。当用户执行某些操作时,着色器的行为需要更改。这些操作不仅会影响数字常量,也不会影响输入数据,它们会影响一系列计算步骤的数量,顺序和类型。

为了解决这个问题,我想到了两个解决方案:

  1. 在运行时生成着色器代码然后编译它。这非常依赖于CPU,因为编译可能需要一些时间,但它非常适合GPU。
  2. 使用同一着色器在运行时解释的某种字节码。这样就无需再次编译着色器,但现在GPU需要处理大量的簿记。
  3. 我为两种方法开发了原型,结果比我预期的更加极端。

    编译时间在很大程度上取决于着色器的其余部分(我猜有很多函数内嵌),我想我可以重构着色器来减少每个线程的工作量并缩短编译时间。但是,我现在不知道这是否足够,而且我不太喜欢运行时重新编译的想法(非常依赖平台,更难调试,更复杂)。

    另一方面,字节码方法运行(不考虑第一种方法的编译时间)慢25倍。

    我知道字节码方法会变慢,但我没想到这一点,特别是在优化之后。

    解释器通过从统一缓冲区对象读取字节码来工作。这是对它的简化,我在有用(非簿记)代码中放置了一个“...”,该部分与其他方法相同(显然,这不是在一个带有大if / else的循环内部)选择正确的指令):

    layout (std140, binding=7) uniform shader_data{
        uvec4 code[256];
    };
    
    float interpreter(vec3 init){
            float d[4];
            vec3 positions[3];
            int dDepth=0;
            positions[0]=init;
            for (int i=0; i<code[128].x; i+=3){
                const uint instruction=code[i].x;
                const uint ldi=code[i].y;
                const uint sti=code[i].z;
                if (instruction==MIX){
                    ...
                }else{
                    if (instruction<=BOX){
                        if (instruction<=TRANSLATION){
                            if(instruction==PARA){
                                ...
                            }else{//TRANSLATION;
                                ...
                            }
                        }else{
                            if (instruction==EZROT){
                                ...
                            }else{//BOX
                                ...
                            }
                        }
                    }else{
                        if (instruction<=ELLI){
                            if (instruction==CYL){
                               ...
                            }else{//ELLI
                               ...
                            }
                        }else{
                            if (instruction==REPETITION){
                               ...
                            }else{//MIRRORING
                               ...
                            }
                        }
                    }
                }
            }
            return d[0];
        }
    

    我的问题是:你知道为什么这么慢(因为我在翻译中没有看到这么多的簿记)?你能猜出这个翻译的主要性能问题是什么?

1 个答案:

答案 0 :(得分:3)

GPU在最好的时候不喜欢条件分支。因此,字节代码解释是您可以在GPU上执行的最差事项之一。

当然,在您的情况下,分支的主要问题并不是那么糟糕,因为您的&#34;字节代码&#34;一切都在统一的记忆中。即便如此,由于所有分支机构,它的运行速度也会过慢。

在高级别上更好地处理着色器的可能性会更好,然后使用非常小的数量的分支来决定整个着色器的行为将会。这些不会处于字节代码的级别。他们更喜欢用矩阵蒙皮计算位置&#34;或者&#34;使用此BRDF计算照明&#34;或&#34;使用阴影贴&#34;。

这就是所谓的&#34; ubershader&#34;方法:一个着色器,具有许多大而独特的代码路径,由几个统一的设置决定。

如果你不能这样做,那么在需要时你可以在重新编译之外做些什么。这会对CPU造成伤害;您不能指望使用着色器开始重新编译它的帧(或者之后的几帧)。 SPIR-V着色器可能有助于重新编译性能,但可能没那么多。

  

尽管有一个小的延迟(~100ms)并不是那么糟糕,因为它不是游戏

我说测量着色器编译所需的时间。如果它不到100毫秒(或者你认为足够的互动),那就去吧。

但是,请注意,许多OpenGL实现在单独的线程上重新编译着色器。因此,当CREATE INDEX idx_metadata_btree_direction ON analytics.incidents ((metadata #>> '{fakeWeather, windDirection}'));完成时,着色器可能无法完成。要准确地描述此过程,您需要强制重新编译。获得glLinkProgram应该可以解决问题。

还有一个表现技巧:不要使用GL_LINK_STATUSglCompileShader。相反,请改用glLinkProgram。它创建了一个可分离的程序(只包含一个着色器阶段),但该过程可能比必须编译和链接为单独的操作更快。