优化OpenGL ES 应用 (1)

175 篇文章 23 订阅
43 篇文章 6 订阅

9. 优化应用程序

9.1 着色器优化

本节介绍各种技巧和方法,以帮助在Adreno架构上优化OpenGL ES应用程序。

9.1.1 在初始化期间编译和链接

编译和链接着色器是一个耗时的过程。与OpenGL ES中的其他调用相比,它是昂贵的。建议在初始化时加载和编译着色器,然后调用glUseProgram在渲染阶段切换着色器。

对于OpenGL ES 2.0、ES 3.0和ES 3.1上下文,建议使用blob二进制文件。编译和链接一个程序对象之后,可以使用以下函数之一来检索二进制表示形式或blob:

  • glGetProgramBinaryOES -如果使用OpenGL ES 2.0上下文,扩展名为GL_OES_get_program_binary可用
  • glGetProgramBinary -如果使用OpenGL ES 3.0或3.1上下文(核心功能)

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

重点

许多OpenGL ES实现都有一个习惯,就是在链接程序对象时将构建时GL状态合并到程序对象中。如果该程序随后被用于不同GL状态配置上下文中发出的draw调用,那么OpenGL ES实现就必须透明地实时重建该程序。这种行为在OpenGL ES规范中是合法的。然而,它会给开发人员带来严重的问题。重建程序对象通常需要大量时间,这可能导致严重的帧下降。应用程序没有请求重新生成,因此延迟是意外的,其原因可能并不明显。

在Adreno平台上,这不是问题。Adreno驱动程序从不重新编译着色器。可以肯定的是,除非在特定的请求下,程序对象不会被重新构建。

9.1.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/,它阐明了着色器语言版本对每个函数的支持。

9.1.3 使用适当的数据类型

在代码中使用最合适的数据类型可以使编译器和驱动程序优化代码,包括对着色器指令。

使用vec4数据类型代替float可以防止编译器执行上述优化。小的错误可能会对性能产生很大的影响。

另一个例子是下面的代码应该采用单条指令:

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

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

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

现在代码将消耗8条指令。将变量a转换为vec4,然后用浮点数进行加法。最后,将结果转换回返回类型int4。

9.1.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; 
} 

9.1.5 封装标量常数

将标量常量封装到由四个通道组成的向量中,大大提高了硬件的获取效率。在动画系统的情况下,这增加了可用于蒙皮的框架数量。

考虑以下代码:

float scale, bias; 
vec4 a = Pos * scale + bias; 

通过如下修改代码,它可以减少一条指令,因为编译器可以优化这行代码,使其更高效:

vec2 scaleNbias; 
vec4 a = Pos * scaleNbias.x + scaleNbias.y;

9.1.6 保持着色器长度合理

过长的着色器可能是低效的。如果需要在着色器涉及纹理获取的数量中包含大量的指令,那么请考虑将算法分成几个部分。算法的一部分生成的值可以存储到纹理中,然后通过纹理获取获取。然而,这种方法在内存带宽方面可能会很昂贵。使用三线性的,各向异性的过滤,宽纹理格式,3D和立方体贴图纹理,纹理投影,使用不同LoD的梯度的纹理查找,或跨像素四象素的梯度也可能增加纹理采样时间,降低整体效益。

9.1.7 有效的纹理采样方式

为了避免纹理卡住,请遵循以下规则:

  • 避免随机访问-硬件运行在2x2片段的块上,所以着色器在单个块内访问相邻的texel会更有效率。
  • 避免3D纹理-从体纹理获取数据是昂贵的,因为需要执行复杂的过滤来计算结果值
  • 限制从着色器中采样的纹理数量——在一个着色器中使用四个采样器是可以接受的,但是在一个着色器阶段访问更多的纹理可能会导致性能瓶颈。
  • 压缩所有的纹理-这允许更好的内存使用,转换到渲染管道中较少的纹理停滞。
  • 考虑使用mipmaps——mipmaps有助于合并纹理获取,并有助于以增加内存使用量为代价提高性能。

一般来说,三线性和各向异性的滤波比双线性滤波的开销要大得多,而最临近滤波和双线性滤波的开销没有区别。

纹理滤波会影响纹理采样的速度。在32位格式的2D纹理中查找双线性纹理只需要一个周期。加上三线性滤波,成本翻倍到两个周期。然而,限制Mipmap可能会将这一成本降低到双线性,因此在实际情况下,平均成本可能会更低。添加各向异性过滤与各向异性的程度相乘。这意味着16倍的各向异性查找比常规的各向同性查找要慢16倍。然而,由于各向异性过滤是自适应的,这个命中仅在需要各向异性过滤的片段上。总共可能只有几个片段。 实际情况的经验法则是,各向异性过滤的平均成本不到各向同性成本的两倍。

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

注意:这些特定于着色器的梯度不能在查找过程中存储。如果在相同的采样器中再次使用相同的梯度进行纹理查找,它将再次触发一个循环。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值