首页 > 其他分享 >OpenGL RHI优化

OpenGL RHI优化

时间:2024-10-14 11:00:52浏览次数:7  
标签:OpenGL buffer pc0 uniform RHI vec4 define 优化 View

前言

随着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

 

标签:OpenGL,buffer,pc0,uniform,RHI,vec4,define,优化,View
From: https://www.cnblogs.com/ghl_carmack/p/18458877

相关文章

  • 数据倾斜优化实践
    dws数据库,根据此语句selecttable_distribution("库名","表名")查看表是否倾斜,常规判断方法:不同DN中的数据行数相差超过10%就认定为数据发生倾斜。实际解决办法:1、调整分区字段,此方法不一定有效,需要不断尝试变换分区字段,并反复确认是否发生数据倾斜。         ......
  • 揭秘多层PCB过孔的优化策略
    多层板中过孔(via)不仅是连接不同层间电路的桥梁,也是确保电路可靠性和性能的关键因素。过孔的成本占据了PCB制造成本的显著部分,通常在30%到40%之间。每一个孔,无论是用于电气连接还是器件固定,都是过孔家族的一员。过孔根据其在PCB中的位置和功能,主要分为三类:1.盲孔(BlindVi......
  • Spark之RDD内核原理,MR的原理计算回顾,RDD的洗牌(shuffle)过程,RDD优化之避免shuffle过程
    学习:知识的初次邂逅复习:知识的温故知新练习:知识的实践应用目录一,MR的shuffle回顾1,Map阶段:2,Shuffle阶段:3,Reduce阶段:二,spark的shuffle介绍 1,两种洗牌的方式2,spark的计算是要尽量避免进入shuffle计算三,并行度1,资源并行度 2,数据并行度一,MR的shuffle回顾1,M......
  • 从2s优化到0.1s,我用了这5步
    前言分类树查询功能,在各个业务系统中可以说随处可见,特别是在电商系统中。但就是这样一个简单的分类树查询功能,我们却优化了5次。到底是怎么回事呢?背景我们的网站使用了SpringBoot推荐的模板引擎:Thymeleaf,进行动态渲染。它是一个XML/XHTML/HTML5模板引擎,可用于Web与非Web环......
  • 网站关键字标签的作用与优化策略
    网站关键字标签是搜索引擎优化(SEO)的重要组成部分。关键字标签可以帮助搜索引擎理解网站内容,并将合适的用户引导到网站。本文将介绍关键字标签的作用以及如何优化它们。1.关键字标签的作用关键字标签是网站HTML页面中用于描述页面内容的简短文本。这些标签告诉搜索引擎关于......
  • MySQL中的数据类型有哪些?如何选择合适的数据类型来优化性能?
    MySQL中的数据类型丰富多样,它们主要分为以下几大类,并且每一类中又包含多种具体的数据类型:整数类型:包括TINYINT、SMALLINT、MEDIUMINT、INT、BIGINT等,这些类型用于存储整数值,其范围和存储大小各不相同。例如,TINYINT占用1个字节,而BIGINT则占用8个字节。浮点数和定点数类型:FLO......
  • 无约束优化问题
    收敛速度由算法A产生的迭代序列${xk}$在某种意义下收敛到$x$即$\lim_{k\to\infty}\left|xk-x\right|=0$,且存在常数$\alpha>0,q>0$$$s.t.\lim_{k\to\infty}\frac{\left|x{k+1}-x*\right|}{\left|xk-x*\right|^{\alpha}}=q$$则称算法A产生的点列${x^k}$......
  • 高性能计算学习笔记-优化(2)
    一、Loop循环优化有以下几种循环合并:两个循环合并到一个循环中循环展开:循环内的并行技术循环交换:改变多维数组的空间访问顺序,改善空间局部性,提高cache命中率循环分布:将一个循环拆分为多个循环,使编译器可以进行向量化优化循环不变:循环中不发生变化的量提到循环外面,避免重......
  • 基于牛顿拉夫逊算法优化长短期记忆网络结合注意力机制(NRBO-LSTM-Attention)(多输入多
    文章目录效果一览文章概述部分源码参考资料效果一览文章概述基于牛顿拉夫逊算法优化长短期记忆网络结合注意力机制(NRBO-LSTM-Attention)(多输入多输出)(多输入多输出)MATLAB完整源码和数据纯手工制作,代码质量极高,注释清晰,excel数据,方便替换1.data为数据集,10个......
  • java中HashMap扩容机制详解(扩容的背景、触发条件、扩容的过程、扩容前后的对比、性能
    在Java中,HashMap是一个非常常用的数据结构,基于哈希表实现,它通过键值对的形式存储数据。为了保证其操作的效率,HashMap采用了一种动态扩容机制。当HashMap中元素数量增长到一定程度时,会自动进行扩容。本文将详细讲解HashMap的扩容机制,包括其触发条件、过程、及扩容过程中可能......