优化OpenGL ES应用程序的小技巧(1)

大家好,今天小白给大家简答一些优化OpenGL ES应用程序的小技巧, 欢迎一起交流学习.

一, 优化OpenGL ES应用程序

1, 在初始化期间编译和链接

着色器的编译和链接是一个耗时的过程。 与OpenGL ES中的其他调用相比,它很昂贵。 建议在过程中加载和编译着色器
初始化,然后调用glUseProgram以在渲染阶段根据需要在着色器之间切换。
对于OpenGL ES 2.0,ES 3.0和ES 3.1上下文,建议使用blob二进制文件。 编译和链接程序对象后,可以检索二进制表示,或
blob,使用以下功能之一:

     a, glGetProgramBinaryOES  - 如果使用OpenGL ES 2.0上下文GL_OES_get_program_binary扩展可用.

     b, glGetProgramBinary  - 如果使用OpenGL ES 3.0或3.1上下文(核心功能)

然后可以将blob保存到持久存储中。 下次启动应用程序时,无需重新编译和重新链接着色器。 相反,从持久存储中读取blob并使用glProgramBinaryOES或glProgramBinary将其直接加载到程序对象中。 这可以显着加快应用程序的启动时间。

2, 使用内置插件

内置函数是OpenGL ES着色语言规范的重要组成部分,应始终优先用于编写自定义实现。这些函数通常针对特定着色器配置文件以及为其编译着色器的硬件功能进行了优化。 因此,它们通常比任何其他实现更快。内置函数在OpenGL ES着色语言规范中描述:

https:// www.khronos.org/registry/gles/specs/3.1/GLSL_ES_Specification_3.10.pdf, 它们也包含在OpenGL ES参考页面中:https://www.khronos.org/opengles/sdk/ docs/man31/, 它阐明了每个功能的Shader Language版本支持。

3, 使用适当的数据类型

在代码中使用最合适的数据类型可以使编译器和驱动程序优化代码,包括着色器指令的配对。使用vec4数据类型而不是float可能会阻止编译器执行上述优化, 小错误会对性能产生很大影响。另一个例子是以下代码应该采用单个指令槽:

int4 ResultOfA(int4 a)
{
return a + 1; }

现在假设代码中引入了一个小错误。 例如,使用浮点常数值1.0,这不是适当的数据类型。

int4 ResultOfA(int4 a)
{
return a + 1.0;
}

代码现在可以消耗8个指令槽。 变量a转换为vec4,然后,加法以浮点完成。 最后,结果将转换回返回类型int4。

4, 减少类型转换

还建议减少执行的类型转换操作的数量。 以下代码可能不是最理想的:

uniform sampler2D ColorTexture;
in vec2 TexC;
vec3 light(in vec3 amb, in vec3 diff)
{
    vec3 Color = texture(ColorTexture, TexC);
    Color *= diff + amb;
    return Color;

}

这里,对纹理函数的调用返回一个vec4。 vec3有一个隐式类型转换,需要一个指令槽。 如下更改代码可能会减少指令
编号为:

uniform sampler2D ColorTexture;
in vec2 TexC;
vec4 light(in vec4 amb, in vec4 diff)
{
    vec4 Color = texture(Color, TexC);
    Color *= diff + amb;
    return Color;

}

5, 保持着色器长度合理

过长的着色器可能效率低下。如果需要在着色器中包含相对于纹理提取数量的大量指令槽,请考虑将算法分成几个部分。 由算法的一部分生成以供稍后由另一部分重用的值可以存储到纹理中,然后通过纹理提取来检索。 然而,就存储器带宽而言,这种方法可能是昂贵的。 三线性,各向异性过滤,宽纹理格式,3D和立方体贴图纹理,纹理投影,具有不同LoD渐变的纹理查找或像素四边形上的渐变的使用也可以增加纹理采样时间减少整体效益。

6, 以有效的方式对纹理进行采样

要避免纹理停顿,请遵循以下规则:

    a. 避免随机访问 - 硬件在2x2片段的块上运行,因此如果着色器访问单个块中的相邻纹素,则效率更高。

    b. 避免使用3D纹理 - 由于需要执行复杂的过滤来计算结果值,因此从体积纹理中获取数据非常昂贵。

    c. 限制从着色器采样的纹理数量 - 在单个着色器中使用四个采样器是可以接受的,但在单个着色器阶段访问更多纹理可能会导  致性能瓶颈。

    d. 压缩所有纹理 - 这允许更好的内存使用,转换为渲染管道中较少数量的纹理停顿。

通常,三线性和各向异性滤波比双线性滤波要昂贵得多,而最近和双线性滤波之间的成本没有差别。

纹理过滤可以影响纹理采样的速度。 在32位格式的2D纹理中进行双线性纹理查找需要一个周期。 添加三线性过滤会使成本增加两倍。 Mipmap钳位可能会降低双线性成本,因此在实际情况下平均成本可能会降低。 添加各向异性过滤与各向异性程度相乘。 这意味着16x各向异性查找可能比常规各向同性查找慢16倍。 但是,由于各向异性过滤是自适应的,因此仅对需要各向异性过滤的片段进行此命中。它可能只是几个片段,  对于现实世界的案例,经验法则是各向异性过滤平均不到各向同性成本的两倍。

立方体贴图纹理和投影纹理查找不会产生任何额外成本,而基于dFdx和dFdy函数的着色器特定渐变会花费额外的周期。 这意味着通常需要一个周期的常规双线性查找将使用着色器特定的渐变进行两次。

7, 打包着色器插值器

着色器插值或变换需要GPR来保存馈送到片段着色器的数据。 因此,尽量减少使用。使用值均匀的常量。 将值组合在一起,因为所有变化都有四个组成部分,无论它们是否被使用。 将两个vec2纹理坐标放入单个vec4值是一种常见做法,但其他策略采用更具创意的打包和动态数据压缩。 OpenGL ES 3.0和ES 3.1引入了各种内部函数来执行打包操作。

8, 最大限度地减少着色器GPR的使用

最小化GPR的使用可以是优化性能的重要手段。 向编译器输入更简单的着色器有助于保证最佳结果。 修改GLSL甚至可以保存
单指令有时可以保存GPR。 不展开循环也可以保存GPR,但这取决于着色器编译器。 始终对着色器进行分析,以确保所选择的最终解决方案是目标平台中最有效的解决方案。 展开的循环倾向于将纹理提取放置到着色器顶部,导致需要更多的GPR来同时保存多个纹理坐标和获取结果。

例如,如果展开下面显示的循环:

for (i = 0; i < 4; ++i) {
diffuse +=ComputeDiffuseContribution(normal,light[i]);
}

The code snippet would be replaced with:

diffuse += ComputeDiffuseContribution(normal, light[0]);
diffuse += ComputeDiffuseContribution(normal, light[1]);
diffuse += ComputeDiffuseContribution(normal, light[2]);
diffuse += ComputeDiffuseContribution(normal, light[3]);

9, 最小化着色器指令计数

编译器优化特定指令,但它不会自动生效。 分析着色器以尽可能保存指令。 保存一条指令是值得的。

二, 总结

总结:本篇主要是简单的分享了一部分优化OpenGL ES应用程序的小技巧,更多相关内容详见下一篇,欢迎一起学习交流.

你可能感兴趣的:(OpenGLES)