OpenGL RHI优化

前言

随着Vulkan的普及,OpenGL已经在被慢慢淘汰,更轻的API调用可以节省不少性能,尤其是在移动平台上,可以减少CPU开销,进而减少功耗。看起来很完美,但是问题是目前移动平台Vulkan驱动存在很多兼容性问题,大家主流的做法都是通过白名单的方式去开Vulkan,所以目前我们还是要继续以OpenGL为主。此文的目的是笔者在优化OpenGL的时候积累的一些经验,因为使用的引擎是UE4,所以这里的优化是以UE4展开的,当然大部分优化都是通用的。

优化

在诸多API中,耗时比较高的有如下这些

  1. 设置texture
  2. 设置buffer
  3. 设置uniform、uniform buffer
  4. 设置program
  5. 更新texture
  6. 更新buffer
  7. 编译shader

其它API也有开销,但是不是特别明显或者尽量避免即可(比如设置render target),可以针对性做些优化,一般状态缓存就能比较好的解决问题。

因为移动平台目前主流机器都是TBDR构架,不同平台有自己的减少overdraw的策略,比如高通的LRZ、ARM的FPK以及PowerVR的HSR技术。所以我们排序可以以渲染状态为主来排序,当然老的机器上因为实现不好,可能还是按距离排序能减少更多overdraw。接下来我们针对上面提到的开销大的API针对性做优化。

设置texture

  1. 尽量Pack纹理通道,比如Normal使用两个通道
  2. 使用Atlas合并贴图
  3. 使用Texture2DArray合并贴图
  4. 将通用的纹理固定到特定slot上,比如shadow map,reflection texture,cluster shading 相关buffer等

SHADER_PARAMETER_TEXTURE_EX(Texture2D, DirectionalLightShadowTexture, 3)

  1. UE每个DC设置完后会把没用到的texture置成None,这样是为了解决某些驱动的问题,可以优化,太过于保守了。

设置Buffer

  1. 相关性比较强的buffer尽量放到一起,比如normal和tangent
  2. 使用大buffer+offset的方式管理buffer,这个在后面更新buffer会详细讲解

设置uniform、unform buffer

在4.21之前,ES31下面是完全使用uniform buffer,从4.21之后可以使用emulated uniform buffer,这个东西就是你上层设置更新还是使用的uniform buffer的接口,但是实际上底层用的是uniform。按官方的说法是可以节省大量的内存并且会提升性能

但是实际上我们测试下来开销还是很高,因为设置的uniform数量会变很多,那么有没有更好的优化方式呢?当然是有的,既然是想省内存和性能,那么我们可以使用混合的方式,让uniform和uniform buffer共存使用。哪些适合用uniform buffer呢,像View、DirectionalLight、Shadow这种per frame或者multi frame的就适合,因为数量少,但是像Primitive这种数量特别大的就不适合。

另外UE本身实现的emulated uniform buffer因为在使用的时候并没有把数据完全Pack起来,这个地方也可以在编译期将它们pack到一起并记录下来运行时拷贝到对应的offset处。

|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 优化前 | 优化后 |
| #define View_IndirectLightingCacheShowFlag (pc0_h[11].x) #define View_ReflectionEnvironmentRoughnessMixingScaleBiasAndLargestWeight (pc0_h[10].xyz) #define View_HighResolutionReflectionCubemapMaxMip (pc0_h[9].x) #define View_ReflectionCubemapMaxMip (pc0_h[8].x) #define View_SkyLightColor (pc0_h[7].xyzw) #define View_NormalCurvatureToRoughnessScaleBias (pc0_h[6].xyz) #define View_IndirectLightingColorScale (pc0_h[5].xyz) #define View_CullingSign (pc0_h[4].x) #define View_PreExposure (pc0_h[3].x) #define View_ViewSizeAndInvSize (pc0_h[2].xyzw) #define View_ViewRectMin (pc0_h[1].xyzw) #define View_PreViewTranslation (pc0_h[0].xyz) uniform highp vec4 pc0_h[12]; | layout(std140) uniform pb0 { vec4 Padding0[76];  highp vec3 View_PreViewTranslation; float PaddingF1228_0; vec4 Padding1228[63]; vec4 View_ViewRectMin; highp vec4 View_ViewSizeAndInvSize; vec4 Padding2272[4]; float PaddingB2272_0; highp float View_PreExposure; float PaddingF2344_0; float PaddingF2344_1; vec4 Padding2344[6]; float PaddingB2344_0; float PaddingB2344_1; float PaddingB2344_2; highp float View_CullingSign; vec4 Padding2464[13]; highp vec3 View_IndirectLightingColorScale; float PaddingF2684_0; vec4 Padding2684[54]; highp float View_IndirectLightingCacheShowFlag; } View; |
| #define Primitive_LightingChannelMask (pc2_u[0].x) #define Primitive_UseSingleSampleShadowFromStationaryLights (pc2_h[1].x) #define Primitive_InvNonUniformScaleAndDeterminantSign (pc2_h[0].xyzw) uniform uvec4 pc2_u[1]; uniform highp vec4 pc2_h[3]; | #define Primitive_PrimaryPrecomputedShadowMaskValue (pc2_h[1].z) #define Primitive_LightingChannelMask (floatBitsToUint(pc2_h[1].y)) #define Primitive_UseSingleSampleShadowFromStationaryLights (pc2_h[1].x) #define Primitive_InvNonUniformScaleAndDeterminantSign (pc2_h[0].xyzw) uniform highp vec4 pc2_h[2]; |

可以看到View使用了uniform buffer,而Primitve还是使用uniform,但是变量数量从4个vec4减少到了两个vec4。

设置Program

尽量减少program的数量,比如一些简单的宏可以通过?运算符之类来避免,另外是通过uniform的方式来代替宏,当然这个需要评估,因为可能会造成register spilling以及降低效率。

更新纹理

在开启了texture streaming之后并且纹理数量过多的情况下会导致纹理更新的消耗比较大,可以尝试以下优化:

  1. UE本身使用了PBO来做纹理更新,这个在移动平台上没必要的,还额外多了一次上传PBO的开销。
  2. 另外在开启RHI情况下会有一次额外的从Render到RHI的纹理数据拷贝,这个也可以优化掉。
  3. OpenGL本身支持multi context,可以单独起一个线程来做纹理的上传。

更新Buffer

如果你的buffer数量很多另外又需要频率的更新,这个时候在一些稍微老些的机器上(888及以下机器)很容易遇到更新buffer的过高耗时和卡顿,我们在之前的文章里面有写过。

只不过当时的文章比较久了,后面又有新的实现,现在是除了UAV之外的所有buffer都可以使用大buffer+offset方式访问内存,这个给RHI减少10%~20%的开销。

  1. glDrawRangeElements、glDrawElements 中有start index
  2. texture buffer glTexBufferRangeEXT 支持offset,这个主要是ISM、HISM中的instance数据会用到。

Shader编译

Shader编译是很耗时的操作,目前大家常见的做法就是提前收集好PSO并预热,但是很难覆盖完整,如果直接在RHI线程编译会导致卡顿,这个时候也可以复用GL的多context机制进行异步编译。但是这样会引入闪烁,需要去做平衡。

总结

上面列了一些OpengGL开销较大的函数并针对性做了优化,其它API也可以通过cache机器等来做优化,如果按照上面的思路都优化完成,相信你的GL性能一定会有不错的提升以及更低的功耗。

参考

  1. https://www.unrealengine.com/en-US/blog/unreal-engine-4-21-released
  2. https://registry.khronos.org/OpenGL/extensions/EXT/EXT_texture_buffer_object.txt
相关推荐
神经网络与数学建模10 天前
AHA-RF|人工蜂鸟-随机森林-回归-降维|多变量特征筛选降维-回归预测|Matlab
算法·随机森林·机器学习·matlab·回归·优化·预测
极客代码14 天前
C语言性能优化:从基础到高级的全面指南
c语言·开发语言·性能优化·性能
vivo互联网技术17 天前
主打一个“小巧灵动”:Vite + Svelte
vite·性能·svelte·轻量·研发效率
布兰妮甜23 天前
图片和媒体资源的优化:提升Web应用性能与用户体验的关键
前端·媒体·优化·图片
Amd7941 个月前
Nuxt.js 应用中的 afterResponse 事件钩子
安全·日志·nuxt·清理·性能·钩子·响应
Amd7941 个月前
Nuxt.js 应用中的 beforeResponse 事件钩子
安全·nuxt·性能·用户·处理·钩子·响应
Amd7941 个月前
Nuxt.js 应用中的 request 事件钩子
安全·nuxt·性能·请求·处理·钩子·实践
Winston Wood2 个月前
Linux中的共享内存
linux·内存·共享内存·进程通信·性能
Winston Wood2 个月前
Java线程池详解
java·线程池·多线程·性能
控心つcrazy2 个月前
图片渐进式加载优化实践指南
优化·加载·图片优化·渐进式