前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >GLSL 的若干优化策略

GLSL 的若干优化策略

作者头像
音视频开发进阶
发布2022-10-31 11:10:27
5580
发布2022-10-31 11:10:27
举报

一个好的 Shader,特别是在低端机上跑效果,性能往往会有很大的提升,那么,就很有必要学习一下 GLSL Shader 性能优化的策略。

下面整理了一些优化的策略。

1. 延迟 vector 计算

不好的用法:

代码语言:javascript
复制
  highp float f0,f1;
  highp vec4 v0,v1;
  v0 = (v1 * f0) * f1;

优化后的用法:

代码语言:javascript
复制
  highp float f0,f1;
  highp vec4 v0,v1;
  V0 = v1 * (f0 * f1)

2. 去冗余计算,vector 整体计算

良好的用法:

代码语言:javascript
复制
  highp vec4 v0;
  highp vec4 v1;
  highp vec4 v2;
  v2.xz = v0 * v1;

3. 避免分支语句(if语句和个别for语句)。

下面是分支语句的性能排序:

a)最佳:编译确定的常量

b)可接受:unoform变量

c)可能很差:在shader内计算的变量。

其解决方案:将各个分支座位单独的shader(可能会增加一点工作量以及复杂度)。

实践参考:在性能和工量入复杂度之间作一个权衡。

因受寄存器限制,SL的编译代码越短,效率越高。另外,因为是海量计算,所以细徽的优化会带来革命性的性能改善。

4. 使用 glsl_optimizer 优化工具进行优化

glsl_optimizer 是一个免费开源的glsl优化器。可以生成GPU无关的shader优化代码。

可以进行非常多的优化项目,比如 函数内联,死代码删除,常量折叠,常量传递,数学优化等等。

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 操作也是很费事的。

8. Alpha Test

Alpha test 和 clip() 函数,在不同平台有不同的性能开销。

通常使用它来剔除那些完全透明的像素。

但是,在 iOS 和一些 Android 上使用的 PowerVR GPUs上面,alpha test非常的昂贵。

9. Color Mask

在移动设备上,Color Mask 也是非常昂贵的,所以尽量别使用它,除非真的是需要。

10. For和If不一定意味着动态分支

在GPU上的分支语句(for,if-else,while),可以分为三类。

  • Branch 的 Condition 仅依赖编译期常数

此时编译器可以直接摊平分支,或者展开(unloop)。

对于For来说,会有个权衡,如果For的次数特别多,或者body内的代码特别长,可能就不展开了,因为会指令装载也是有限或者有耗费的额外成本可以忽略不计。

  • Branch的 Condition 仅依赖编译期常数和Uniform变量

一个运行期固定的跳转语句,可预测同一个Warp内所有micro thread均执行相同分支, 额外成本很低

  • Branch 的 Condition 是动态的表达式

这才是真正的“动态分支” ,会存在一个Warp的 Micro Thread 之间各自需要走不同分支的问题。

11. 跳转本身的成本非常低

随着IP/EP(Instruction Pointer/Execution Pointer)的引入,现代GPU在执行指令上的行为,和CPU没什么两样。跳转仅仅是重新设置一个寄存器。

12. Micro Thread 走不同分支时的处理

GPU本身的执行速度快,是因为它一条指令可以处理多个 Micro Thread 的数据(SIMD)。

但是这需要多个 Micro Thread 同一时刻的指令是相同的。

如果不同,现代GPU通常的处理方法是,按照每个Micro Thread的不同需求多次执行分支。

代码语言:javascript
复制
x = tex.Load();
if(x == 5)
{
// Thread 1 & 2 使用这个路径
out.Color = float4(1, 1, 1, 1);
}
else
{
// Thread 3 & 4 使用这个路径
out.Color = float4(0, 0, 0, 0);
}

比如在上例中,两个分支的语句Shader Unit都会执行,只是不同的是如果在执行if分支,那么计算结果将不会写入到thread 3 和 4的存储中(无副作用)。

这样做就相当于运算量增加了不少,这是动态分支的主要成本。

但是如果所有的线程,都走的是同一分支,那么另外一个分支就不用走了。

这个时候Shader Unit也不会去×××一样的执行另外一个根本不需要执行的分支。此时性能的损失也不多。

并且,在实际的Shader中,除非特殊情况,大部分Warp内的线程,即便在动态分支的情况下,也多半走的是同一分支。

13. 动态分支和代码优化难度有相关性

这一点经常被忽视,就是有动态分支的代码,因为没准你要读写点什么,前后还可能有依赖,往往也难以被优化。

比如说你非要闹这样的语句出来:

代码语言:javascript
复制
if(x == 1)
{
color = tex1.Load(coord);
}
else if(x == 2)
{
color = tex2.Load(coord);
}

你说编译器怎么给你优化。

说句题外话,为啥要有TextureArray呢?

也是为了这个场合。TextureArray除了纹理不一样,无论格式、大小、坐标、LoD、偏移,都可以是相同的。

这样甚至可以预见不同Texture Surface上取数据的内存延迟也是非常接近的。这样有很多的操作都可以合并成SIMD,就比多个Texture分别来取快得多了。

这就是一个通过增加了约束(纹理格式、大小、寻址坐标)把SISD优化成SIMD的例子

来源:https://blog.51cto.com/31329846/2118287

最后欢迎大家加入 音视频开发进阶 知识星球 ,这里有知识干货、编程答疑、开发教程,还有很多精彩分享。

更多内容可以在星球菜单中找到,随着时间推移,干货也会越来越多!!!

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-07-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 音视频开发进阶 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档