OpenGL RHI优化
前言
随着Vulkan的普及,OpenGL已经在被慢慢淘汰,更轻的API调用可以节省不少性能,尤其是在移动平台上,可以减少CPU开销,进而减少功耗。看起来很完美,但是问题是目前移动平台Vulkan驱动存在很多兼容性问题,大家主流的做法都是通过白名单的方式去开Vulkan,所以目前我们还是要继续以OpenGL为主。此文的目的是笔者在优化OpenGL的时候积累的一些经验,因为使用的引擎是UE4,所以这里的优化是以UE4展开的,当然大部分优化都是通用的。
优化
在诸多API中,耗时比较高的有如下这些
- 设置texture
- 设置buffer
- 设置uniform、uniform buffer
- 设置program
- 更新texture
- 更新buffer
- 编译shader
其它API也有开销,但是不是特别明显或者尽量避免即可(比如设置render target),可以针对性做些优化,一般状态缓存就能比较好的解决问题。
因为移动平台目前主流机器都是TBDR构架,不同平台有自己的减少overdraw的策略,比如高通的LRZ、ARM的FPK以及PowerVR的HSR技术。所以我们排序可以以渲染状态为主来排序,当然老的机器上因为实现不好,可能还是按距离排序能减少更多overdraw。接下来我们针对上面提到的开销大的API针对性做优化。
设置texture
- 尽量Pack纹理通道,比如Normal使用两个通道
- 使用Atlas合并贴图
- 使用Texture2DArray合并贴图
- 将通用的纹理固定到特定slot上,比如shadow map,reflection texture,cluster shading 相关buffer等
SHADER_PARAMETER_TEXTURE_EX(Texture2D, DirectionalLightShadowTexture, 3)
- UE每个DC设置完后会把没用到的texture置成None,这样是为了解决某些驱动的问题,可以优化,太过于保守了。
设置Buffer
- 相关性比较强的buffer尽量放到一起,比如normal和tangent
- 使用大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之后并且纹理数量过多的情况下会导致纹理更新的消耗比较大,可以尝试以下优化:
- UE本身使用了PBO来做纹理更新,这个在移动平台上没必要的,还额外多了一次上传PBO的开销。
- 另外在开启RHI情况下会有一次额外的从Render到RHI的纹理数据拷贝,这个也可以优化掉。
- OpenGL本身支持multi context,可以单独起一个线程来做纹理的上传。
更新Buffer
如果你的buffer数量很多另外又需要频率的更新,这个时候在一些稍微老些的机器上(888及以下机器)很容易遇到更新buffer的过高耗时和卡顿,我们在之前的文章里面有写过。
只不过当时的文章比较久了,后面又有新的实现,现在是除了UAV之外的所有buffer都可以使用大buffer+offset方式访问内存,这个给RHI减少10%~20%的开销。
- glDrawRangeElements、glDrawElements 中有start index
- texture buffer glTexBufferRangeEXT 支持offset,这个主要是ISM、HISM中的instance数据会用到。
Shader编译
Shader编译是很耗时的操作,目前大家常见的做法就是提前收集好PSO并预热,但是很难覆盖完整,如果直接在RHI线程编译会导致卡顿,这个时候也可以复用GL的多context机制进行异步编译。但是这样会引入闪烁,需要去做平衡。
总结
上面列了一些OpengGL开销较大的函数并针对性做了优化,其它API也可以通过cache机器等来做优化,如果按照上面的思路都优化完成,相信你的GL性能一定会有不错的提升以及更低的功耗。
参考
- https://www.unrealengine.com/en-US/blog/unreal-engine-4-21-released
- https://registry.khronos.org/OpenGL/extensions/EXT/EXT_texture_buffer_object.txt
OpenGL RHI优化的更多相关文章
- iOS 中OpenGL ES 优化 笔记 1
1,避免同步和Flushing操作 OpenGL ES的命令执行通常是在command buffer中积累一定量的命令后,再做批处理执行,这样效率会更高:但是一些OpenGL ES命令必须flush ...
- 3D Computer Grapihcs Using OpenGL - 13 优化矩阵
上节说过矩阵是可以结合的,而且相乘是按照和应用顺序相反的顺序进行的.我们之前初始化translationMatrix和rotationMatrix的时候,第一个参数都是使用的一个初始矩阵 glm::m ...
- Mali GPU OpenGL ES 应用性能优化--基本方法
1. 经常使用优化工具 watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvTXlBcnJvdw==/font/5a6L5L2T/fontsize/400/fil ...
- opengl performance optimization
OpenGL 性能优化 作者: Yang Jian (jyang@cad.zju.edu.cn) 日期: 2009-05-04 本文从硬件体系结构.状态机.光照.纹理.顶点数组.LOD.Cull等方面 ...
- 开源免费跨平台opengl opencv webgl gtk blender, opengl贴图程序
三维图形的这是opengl的强项,大型3D游戏都会把它作为首选.图像处理,是opencv的锁定的目标,大多都是C的api,也有少部分是C++的,工业图像表现,图像识别,都会考虑opencv的.webg ...
- UE4 RHI与Render模块简解
UE4中的RHI指的是Render hardware interface,作用像Ogre里的RenderSystem,针对Dx11,Dx12,Opengl等等平台抽象出相同的接口,我们能方便能使用相同 ...
- OpenGL 资源汇编
本文收集和汇总了 OpenGL 的文档.教程和在线书籍,供学习和开发者參考. OPENGL开发教程:http://www.linuxgraphics.cn/opengl/index.html Open ...
- cocos2d-html5 中的性能优化
游戏开发中,难免会遇到性能瓶颈.图片一多,渲染批次就会直线上升,任何动画都会变得闪动. OpenGL ES优化的问题,主要考虑两个方面:内存存储和运行速度. 2D游戏中的最占内存的就是图片资源,一张图 ...
- 【Mood-12】Android开发相关书籍推荐
新年伊始,找到Android进阶干货若干,2015拜读. 1.Android应用UI设计模式 目前,谷歌Android操作系统在移动市场中风头正劲,并且未来发展势不可挡.<Android应用UI ...
- Android大放送干:书籍、过程、工具等各种全
完全干燥分享,本文收集Android制定必要的书籍.过程.具.新闻和杂志各种资源.它们能让你在Android开发之旅的各个阶段都受益. 入门 <Learning Android(中文版)> ...
随机推荐
- 【Vue】Re10 Webpack 第二部分(Loader)
一.Loader解决的问题: Loader解决的问题是让webpack能够打包其他类型的文件 例如CSS.IMG等非JavaScript脚本文件 在后面我们使用Vue组件文件时也需要VueLoader ...
- 【Vue】15 VueX
[什么是VueX?] VueX是一个专门为Vue.js应用程序开发的状态管理模式, 采用集中式存储管理应用的所有组件状态, 以相应的规则保证按照一种可预测的方式发生改变. 即把多个组件的变量统一放到一 ...
- 国产深度学习框架 OneFlow 是否靠谱?
OneFlow框架的设计目标是实现:一个使用多机多卡就像使用单机单卡一样容易的深度学习框架. 可以说,这是国内最早的深度学习框架之一,也是至今还活着的公司中开发支持力度最低的,也是最缺少技术支持.用户 ...
- 并行化强化学习 —— 初探 —— 并行reinforce算法的尝试 (上篇:强化学习在多仿真环境下单步交互并行化设计的可行性)
强化学习由于难收敛所以训练周期较长,同时由于强化学习在训练过程中起训练数据一般都为实时生成的,因此在训练的同时算法还需要生成待训练的数据,强化学习算法的基本架构可以视作下图:(取自:深度学习中使用Te ...
- 深度解读KubeEdge架构设计与边缘AI实践探索
摘要:解读业界首个云原生边缘计算框架KubeEdge的架构设计,如何实现边云协同AI,将AI能力无缝下沉至边缘,让AI赋能边侧各行各业,构建智能.高效.自治的边缘计算新时代,共同探索智能边缘的新篇章. ...
- 如何在Spring Cloud中实现Nacos客户端登录密码加密
背景 公司规范要求配置文件里不能出现明文的密码.最近项目引入了Nacos作为服务的配置中心,使用的是spring-cloud-starter-alibaba-nacos-config这个包. 基本的b ...
- EF Core 索引器属性(Indexer property)场景及应用
EF Core 索引器属性(Indexer property)场景及应用 简介 EF Core 中的索引器属性(Indexer Property)是指通过一个特殊的属性来访问实体类中的数据,而不必明确 ...
- springboot如何集成Prometheus如何暴露Histogram来获取P99等监控指标
背景 springboot如何集成Prometheus我这里不做详细描述,要想了解集成过程,可以参考一下博客: Spring Boot 使用 Micrometer 集成 Prometheus 监控 J ...
- Docker不同宿主机网络打通
本方式使用docker Swarm集群的方式创建overlay 网络进行打通 背景 因java微服务使用nacos做配置中心,为了解决Nacos服务注册使用Docker容器内网ip问题,使用此方案 前 ...
- “从零到一:如何在鸿蒙OS上启动你的第一个项目”
背景与引言 全球操作系统市场现状如何? 长期以来,Android.iOS.Windows等巨头几乎垄断了整个市场,成为人们日常生活中不可或缺的工具.然而,尽管它们在各自领域有着不可否认的成功,却也逐渐 ...