GLSL优化策略

GLSL优化策略

前言:一个好的shader,特别是在低端机上跑效果,性能往往会有很大的提升,那么,就很有必要学习一下GLSL shader性能优化的策略。下面整理了一些优化的策略。

  • 1.延迟vector计算。

    • 例如:

    • 不好的用法:

        highp float f0,f1;
        highp vec4 v0,v1;
        v0 = (v1 * f0) * f1;
      
    • 优化后的用法

        highp float f0,f1;
        highp vec4 v0,v1;
        V0 = v1 * (f0 * f1)
      
  • 2.去冗余计算, vector整体计算。

    • 例如:

    • 良好的用法

        highp vec4 v0;
        highp vec4 v1;
        highp vec4 v2;
        v2.xz = v0 * v1;
      
  • 3.避免分支语句(if语句和个别for语句),下面是分支语句的性能排序

    • a)最佳:编译确定的常量
    • b)可接受:unoform变量
    • c)可能很差:在shader内计算的变量。

    其解决方案有:

    • 1.将各个分支座位单独的shader(可能会增加一点工作量以及复杂度)。
    • 另外四种方案以及分支语句会影响性能的原因见链接:
  • 4.使用glsl_optimizer 优化工具进行优化

    • glsl_optimizer 是一个免费开源的glsl优化器。可以生成GPU无关的shader优化代码。可以进行非常多的优化项目,比如 函数内联,死代码删除,常量折叠,常量传递,数学优化等等。具体使用方法请自行google。
  • 5.只计算需要计算的东西

    • 尽量减少无用的顶点数据, 比如贴图坐标, 如果有Object使用2组有的使用1组, 那么不要将他们放在一个vertex buffer中, 这样可以减少传输的数据量;避免过多的顶点计算,比如过多的光源, 过于复杂的光照计算(复杂的光照模型);
    • 避免 VS 指令数量太多或者分支过多, 尽量减少 VS 的长度和复杂程度;
  • 6.尽量在 VS 中计算

    • 通常,需要渲染的像素比顶点数多,而顶点数又比物体数多很多。所以如果可以,尽量将运算从 FS 移到 VS,或直接通过 script 来设置某些固定值;
  • 7.浮点数精度相关:

    • float:最高精度,通常32位.
    • half:中等精度,通常16位,-60000到60000。
    • fixed:最低精度,通常11位,-2.0到2.0,1/256的精度。
    • 尽量使用低精度。对于 color 和 unit length vectors,使用fixed,其他情况,根据取值范围尽量使用 half,实在不够则使用 float 。
    • 在移动平台,关键是在 FS 中尽可能多的使用低精度数据。另外,对于多数移动GPU,在低精度和高精度之间转换是非常耗的,在fixed上做swizzle操作也是很费事的。

原文转载与:http://blog.51cto.com/31329846/2118287

一.优化策略:减少使用分支语句

在编写OpenGL shader时,一定要注意减少使用if或for语句,因为这些语句引入分支、会大大降低shader的性能,得不偿失。之所以if语句会对性能有这么大的影响,要从OpenGL的运行机制说起。

二、GPU计算原理:wavefront

  • 以OpenGL通常处理的图像来说,OpenGL的shader在运算的时候,会产生成千上万个线程来对不同的点位区域进行计算,每个线程都使用同一份shader代码、但是处理的数据不同。为了大幅度提高计算速度,OpenGL利用了GPU,而GPU的基本调度单位叫做wavefront(不同平台理念相同、叫法不同,NVIDIA平台叫warp,AMD平台叫wavefront等,本文统称为wavefront)。wavefront是一组线程的组合,既然称之为调度的基本单位,自然是GPU会同时处理属于同一个wavefront的所有线程,因为他们的计算指令(shader)从第一行到最后一行是完全相同的,只是数据不同而已。GPU正是这样通过single instruction multiple thread(SIMT)的方式来进行提速的。这有点类似于CPU中的SIMD加速,只不过CPU中一次SIMD操作只针对一组数据、需要人为编码控制,而GPU的SIMT是从始至终的用相同指令计算所有的线程数据。这样并行度极高,从而大幅提升了性能。

  • wavefront很形象,中文叫做波阵面,如下图所示。可以看出来是多个线程持续不断的同步计算,每次计算指令相同、uniform部分参数相同、定值参数相同,只有传入纹理、varying参数以及一些本地计算数据等不同而已。

  • 但是一旦引入if/for产生分支,wavefront结构就被完全破坏掉了,会产生diverged wavefront。例如原本4个线程组成一个wavefront一直同步计算,突然遇到if语句,3个线程if判断为true,进入A分支;另一个线程if判断为false,进入B分支,此时这4个线程接下来的指令不再相同,原来的这个线程组wavefront就无法同步计算、被迫分开,即为diverged wavefront。这时候,GPU只能分开执行这两个新产生的wavefront。由于GPU计算资源也是一定的,新产生的两个wavefront可能需要排队等待来顺序执行(原来是并行执行),尤其是wavefront大批量diverged的时候;然后新分割出来的wavefront如果要移动到其他GPU计算单元上还需要进行数据复制转移,也是很耗时的行为。这些都严重破坏了并行度,从而导致性能下降。因此,建议最好少使用产生分支的if语句;for语句有时候也会产生分支,也需要注意。
    ##三、分支语句优化思路

  • 1、trick方式跳过if:
    一些简单的场景可以用OpenGL的step方法把if语句替换。例如原本逻辑为:

      float a;
      if(b < 1)
      {
      	a = 1;
      }
      else
      {
      	a = 0.5;
      }
    

可以改为:

	float a;
	float tmp = step(1,b);
	a = tmp * 0.5 + (1 - tmp);

再例如:

	float a;
	if (b && c || d && e)
	{
		a = 1;
	}		
	else
	{
		a = 0.5;
	}

可以改为:

	float a;
	float tmp = step(1,(float)b * (float)c + (float) d * (float)e);
	a = tmp + (1- tmp) * 0.5; 

因为step方法属于shader内置函数,要比直接使用if耗时减少不少。
step函数是OpenGL内置的,它会比较传入的两个参数的大小,进而返回0或1。

  • 2、部分分支可被编译优化:
    编译器有时可以对分支进行一定的优化。If判断条件一般包含三种数据:

  • (1)静态分支:If判断语句仅仅包含常数;

  • (2)uniform数据分支:If判断语句仅仅包含常数或uniform参数;

  • (3)动态分支:其他情况,If判断语句中有动态变化的数据。

    • 按道理来说,静态数据和uniform数据不会变化,编译器应该可以判断并进行编译优化,但是对于Android开发来说,硬件千差万别,目前据我了解,对于OpenGL ES 2.0,基本上大都只能优化静态分支;对于OpenGL ES 3.0,通常可以优化uniform数据分支,部分机型可能可以优化动态分支。
    • 所以,写分支的时候注意分支的类型,并且如果升级到OpenGL ES 3.0,就基本可以使用uniform数据分支而没有明显的性能损失了。
    • 同理,如果for循环的此时是一个整数、即常量,那么也不会产生分支;只有当for循环的次数也是随着点位的不同动态变化的时候才会产生分支。
  • 3、相同区块情况可以使用分支:

一般来说,相邻的点位区域的线程会组合在同一个wavefront中,如果一个分支与位置相关,例如图像上半部分都是黑色,下半部分是彩色;而If判断条件是颜色是否为黑色,那么大部分情况下同一个wavefront的线程都会在if判断后走同一个分支,这样wavefront就不会diverge。或者判断条件是和位置有关的,那么大概率也不会diverge。只要不产生diverge就不会对性能有很大影响。

  • 4、全量代码,但保证某些分支不起作用:

  • 此外,经过测试,假设If可以产生两个分支,将两个分支的指令全部执行完可能还会比使用If判断还要快。下面举例说明。假设当前需要根据a的值来选择计算result的方法,通常代码如下:

     	if(a == 0)
     	{
     		result = funcA();
     	}
     	else
     	{
     		result = funcB();
     	}
    

那么为了减少diverge,以上代码可以改写为:

		resultA = funcA();
		resultB = funcB();
		result = (1- a)* resultA + a * resultB;

如果不是a==0的情况也可以通过step方式来转换。很多情况下,全量执行所有分支的代码比使用If判断还要快,这个可以通过实际测试比较来进行选择。

四、总结

优化思路就是在OpenGL的shader中尽量少使用if/for等分支语句,因为这会破坏GPU的wavefront结构,从而造成性能损失。如果非用不可也可以参考文中提到的4种策略。总体来说,GPU擅长的是计算而非逻辑判断,所以和逻辑有关的事情还是不在GPU上操作为好。

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值