Loading [MathJax]/jax/input/TeX/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >UE4的执行流程和CPU优化

UE4的执行流程和CPU优化

作者头像
quabqi
发布于 2021-11-04 02:54:19
发布于 2021-11-04 02:54:19
2.2K0
举报
文章被收录于专栏:Dissecting UnrealDissecting Unreal

UE4是一个非常庞大的游戏引擎,说是游戏引擎,但其实内部实现的已经和一个小型操作系统差不多了,源码更是海量级别的。在这样海量的源码面前想要搞清楚是怎样运行的本身就不是一件容易的事情,除此外引擎本身是基于多线程设计的,不同线程之间错综复杂的关系更加深了理解引擎的难度。平时在写代码时候,我们也可能更多的只是关注Actor,Component,Level,World以及游戏逻辑怎么写,但很少去研究他们都是怎样运行的,但是如果不了解这些Actor,Component,Level,World,在游戏线程和渲染线程之间是怎样执行的,不清楚内部的运行机制是怎样的,就很容易写出性能糟糕或有各种问题的代码。为了解决这个问题,我整个梳理了一下UE4的大流程,画了一张图,关键点都用颜色标记了出来,让各个环节能够一目了然,这样就可以围绕着这个执行流程,来介绍一些常见的问题和性能优化手段,避免大家写出糟糕的代码。

我们可以看到,引擎启动的时候,会先初始化各个模块,然后就进入了Tick,在Tick中会先执行游戏逻辑,调用World的Tick,然后Tick所有注册需要Tick的Actor和Component,这里会根据注册的阶段分别在不同时期Tick。结束之后会进入绘制视口,会先画场景,在画场景时才相当于是渲染线程这帧真正开始了,然后画UI。然后中间很多地方都穿插着多线程调度。最终我们看到引擎执行一帧大概是像下图所示

这里具体每一步的细节看最上面那张很大的时序图就好了,我觉得非常清楚就不细说了,下面来具体讲解一些关键点和可能的问题以及优化手段:

初始化阶段

可以看到分为了PreInit和Init两步,这里没什么特别的,需要注意一些组件的初始化顺序。

可以看到引擎的Shader是在游戏启动非常早的时候就会准备好,可能都比业务的下载更新流程还要早,这里如果遇到热更要特别小心的处理,处理不好很有可能出现崩溃。在Init中比较关键的一步是创建Viewport,这个就是游戏最终画到的地方,最终会调用到GameInstance的StartGameInstance函数,这里也是业务逻辑的入口。

Tick阶段

在Tick的一开始,会先把场景数据的各种信息比如Transform在渲染线程上刷一遍,因为很多东西是会动的。之后引擎会开始Tick World。这里比较重要的一点是,我们可以看到Tick的对象有很多阶段,平常用的比较多的是PrePhysics(如果没关注过,打开Tick默认会是这里),During Physics, Post Physics这3个地方。为什么要区分这些阶段呢?这是因为UE4是个多线程的引擎,物理是每帧一个很重要的计算流程,物理的计算发生在一个单独的线程上,因此将Tick拆分成这些阶段,就可以让业务代码选择在什么时期执行。因为大部分的组件都是需要先准备好数据,交给物理线程来执行,所以UE4把Tick默认都放在了Pre Physics上,这样当所有组件Tick完,物理线程得到的数据就是最新的。但是考虑到假如你的组件或Actor和物理没任何关系,那么物理线程就会等待逻辑执行,在物理线程开始执行后,由于Durning Physics基本没事情做,又反过来等待物理线程,这样游戏线程的总耗时就会被拉长。因此可以把一些不需要依赖物理的组件放在其他阶段,说不定能起到很好的优化效果。

如上图所示,这样修改后,总的耗时就会减少。同样的道理,只要涉及到多线程或可以改成多线程的地方,比如动画组件,移动组件等,都可以用类似方式来优化,目标就是要尽量把多线程串行执行变成多线程并行执行。

这里额外提一点,Actor和ActorComponent的Tick是分别注册且互相独立的,互相不存在依赖关系,当你关掉Actor的Tick时,ActorComponent的Tick如果是打开状态,是不会受到影响的,所以当你发现不需要Tick的组件时,关掉Actor是不够的,Component也要单独关闭。另外在我们的招聘面试中,这个问题很多人都回答错了。

绘制阶段

可以看到,引擎的绘制是等待业务Tick全部完成后才开始的,绘制发生在渲染线程上,渲染线程做完相关流程后又可能再单独开一个RHI线程(iOS不开RHI单独的线程,安卓会开单独的RHI线程),他们3个线程之间是在不同时间点执行的。渲染线程和RHI线程和游戏线程不一样,游戏线程会把任务提交到渲染的命令队列里,而渲染线程会依次从队列里取任务执行,当没有新任务的时候,就会等待,而当任务特别多的时候,因为游戏线程会在很多阶段触发Flush操作强行等待渲染线程执行到某个位置,就会导致游戏线程等待,一个比较优秀的游戏肯定更希望把所有的线程都跑满,所以在性能优化时,通过观察stat是哪个线程在等待,就可以知道瓶颈是卡在了他等待对应的那个线程上,只要去优化对应的地方就好。

当场景绘制完成之后,才会开始绘制UI,这里也是UE4比较坑的一个地方,假如UI遮挡住了大部分场景,被遮挡住的部分就白画了。所以如果能修改引擎代码的话,可以考虑在绘制开始阶段,先在场景的RT上UI对应的位置写上深度(需要额外处理半透明)或者建一些对应轮廓面片放在镜头近平面上挡住场景对应区域,这样就可以跳过这些像素的绘制。

我们知道绘制这里游戏线程做的事情很少,基本上会阻塞在最后FrameSync,当你有一些很重的工作,但是又和渲染无关,比如网络游戏的解包或其他比较重的逻辑,就可以考虑在绘制这一阶段期间开启一个单独的线程,让子线程去做这些工作,而不是放在前面的Tick阶段。

程序的入口

我们知道所有的C++程序都是从main函数开始的,UE4也不例外,所以只要找到入口,你就可以一步一步跟着上面那张图,调试跟踪到底UE4是怎样执行的。可以在引擎的Launch模块内看到这部分代码。

windows的入口是WinMain,内部会调用到GuardedMain

mac的入口是INT32_MAIN_INT32_ARGC_TCHAR_ARGV,其实展开就是main,内部会调用到objc的NSApp,这个就是系统提供的App对象,具体应用能实现的就只有后面的Delegate,所以UE4实现了UE4AppDelegate

如果有稍微了解过苹果开发,都会知道真正做初始化都会写在applicationDidFinishLaunching这里

这里根据宏决定是否开一个新线程

最后也调用到GuardMain

iOS就是main,内部会调用到objc的UIApplicationMain,这是iOS提供的app入口,具体业务实现的是后面的IOSAppDelegate,跟Mac大同小异。

安卓会在Java的Activity调用回来,具体流程类似不单独截图了。

因此可以看到,UE4的游戏线程基本上是单独启动了一个子线程作为GameThread,并不是App的主线程,所以GameThread卡死或者耗时非常久,也不会导致应用无响应。当你在手机上接任何第三方SDK,如各种安卓或iOS的SDK一般都会从app的主线程调用回调,这时如果直接调用UE4,做一些操作就很有可能发生崩溃或不可预知的问题,所以要通过TaskGraph或其他方式抛到GameThread上再执行对应逻辑。这里是要特别注意的一点。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

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

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

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
UE5的ECS:MASS框架(二)
前面一篇说了Mass框架的内存结构,也就是ECS中的Entity和Component,也用了一个很简单的示例说明Entity和Archetype怎么创建和销毁。然后也了解到MassEntity的对外API接口基本集中在UMassEntitySubsystem中,上一章的例子也只是一个简单的案例,实际Entity有非常多种操作方式,所以我把创建和销毁Entity对应API列在了下面,通过注释可以了解详细是做什么的,详细实现就不多说了,可以参考上一章。
quabqi
2022/01/07
7.8K0
UE5的ECS:MASS框架(二)
UE4/UE5 动画的原理和性能优化
动画在UE4/UE5项目中,往往不仅是GPU和渲染线程开销大户,也是游戏线程的开销大户。按照我的经验,大型游戏项目(尤其是手游)做到中后期,整个项目优化工作做的差不多的时候,你应该也会发现动画的开销会占到整个GameThread的二分之一到三分之二。动画到底是做了什么呢会产生这么多的开销?项目里关于动画的优化也是最容易扯皮的一件事,开发给美术说要砍资源,减少骨骼数,要减少蒙皮面数,否则游戏跑不动,而美术说骨骼数不够根本做不出好的效果,不能优化。但是为什么骨骼数,蒙皮面数会影响到动画的性能呢?难道除了砍资源之外,就没有别的优化手段了吗?为了回答这些问题,我觉得很有必要说一说动画在虚幻引擎内部的执行流程,最后也会说下我在虚幻引擎动画这块推荐的优化手段。
quabqi
2022/09/19
6K1
UE4/UE5 动画的原理和性能优化
UE4的资源管理
开发一个大型游戏,因为美术资源和游戏数据都是海量的,不可能把所有的数据都放在内存里。本身玩家的游戏设备内存大小也是有限的,而大型游戏尤其是3A大作下载下来都几十G上百G,内存根本放不下。因此就需要有一个手段,可以在需要用的时候把他们加载进来,不需要的时候卸载掉,让内存尽可能的节省,把内存尽可能用到最合适的地方。同时又要避免内存和硬盘上的资源频繁加载卸载耗时影响用户体验。可想而知,要做好资源管理,是一件非常麻烦又非常有必要的事情。
quabqi
2021/11/04
3.5K1
UE4的资源管理
UE4 ReplicationGraph分析
ReplicationDriverClassName="/Script/ProjectName.ClassName"
小伏羲
2018/12/18
4.6K0
UE4 ReplicationGraph分析
UE4下玩转react
在web前端领域,在UI制作方面有着悠久的历史,且一直都基于html+js+css技术,更利于技术的沉淀,是否能把web领域的优秀实践借鉴到游戏中呢?
车雄生
2021/11/10
1.3K0
UE4下玩转react
UE5的World Partition
世界分区,是UE5给大世界项目提供的一套新的解决方案。相比于UE4的WorldComposition有了非常多的改进。官网也有很具体的介绍:
quabqi
2024/01/07
3.2K0
UE5的World Partition
UE4/UE5的崩溃,卡死等问题处理
虚幻引擎的业务逻辑开发基本上都是用C++/蓝图,当因为项目代码写的不好遇到Crash等问题时,如果不了解Native程序和引擎底层的一些机制,相比用C#开发业务的Unity或其他完全基于脚本虚拟机的游戏确实要难处理一些。因为业务和引擎代码本身都是基于C++,所以对于解决常规C++的Crash的方法虚幻引擎完全适用,除此外引擎在异常处理上相比于普通的C++程序还是提供了一些额外的方法和工具。本文主要介绍虚幻引擎在处理Crash时的一些做法和经验技巧。
quabqi
2022/10/05
5.6K0
UE4/UE5的崩溃,卡死等问题处理
更专业省心的来了,你没必要研究UE4和Unity官方推流了!
在当今互联网时代,所有的内容制作者都希望尽可能触达到更多的目标受众,那就需要全平台发布内容并且可以轻松跨平台分享,包括手机、平板电脑、个人电脑以及交互式屏幕,用户能畅快的获得高质量的体验。需求催生了一种新的“云渲染”方案,将庞大负载的“渲染”放到云端强大算力的服务器去执行,前端仅仅是展示和交互。
点量云木子
2023/05/18
5420
更专业省心的来了,你没必要研究UE4和Unity官方推流了!
UE4的TripleBuffer
UE4中有一个特殊的容器TripleBuffer,三缓冲,顾名思义,这个容器内确实是有三个Buffer。这个三缓冲,和引擎渲染时候用到的双缓冲三缓冲虽然原理差不多,但并不是同一个东西,而是更广泛意义上的一个容器,是给开发者的做多线程同步来使用的。我们可以看看渲染时候使用单缓冲,使用双缓冲和使用三缓冲是怎么做的。
quabqi
2021/11/04
1K0
UE4的TripleBuffer
UE5的ECS:MASS框架(三)
前面两篇基本上已经把MASS的ECS基础框架都说清楚了。其中最关键的部分:Fragment/Tag等对应的就是传统ECS中的Component,Processor对应的就是传统ECS中的System,而上层的MassGameplay,MassAI,MassCrowd都是基于底层的ECS框架做出来的Gameplay框架,这一篇主要来说下MassGameplay框架的实现。
quabqi
2022/04/02
7.5K0
UE5的ECS:MASS框架(三)
UE4/UE5的RHI(Vulkan为例)
RHI是Render Hardware Interface的缩写,虚幻引擎通过RHI把各个平台的图形API包装成统一接口,供上层渲染来使用,让业务不用过多的关注API细节(实际还得关注RHI细节)。从代码结构上来看,RHI封装的比较贴合于现代的图形API(vulkan, metal, DX12),也支持opengl/opengles。这个接口是广义上的概念,不仅指C++的纯虚基类,也包括一些全局变量,全局函数等,具体形式就像下面RHI.h头文件这样:
quabqi
2021/11/04
6.6K0
UE4/UE5的RHI(Vulkan为例)
UE4的队列TQueue
TQueue是UE4提供的队列容器,完全满足队列的先进先出性质,这里主要用于多线程同步数据。如果比较了解多线程编程的话,那你肯定知道多线程中最常用的一个容器就是消息队列,解决的就是生产者-消费者问题。
quabqi
2021/11/04
3.5K0
UE4的队列TQueue
UE4/UE5的TaskGraph
TaskGraph是虚幻引擎做多线程开发时,一个非常方便好用的任务框架。这套框架具体做了什么呢?简单说就是创建或绑定了多个线程,根据业务需要把任务调度到不同的线程上来执行。从原理和实现上比较近似于苹果objc的GCD,Java的ThreadPoolExecutor,Unity的JobSystem等多线程任务框架。
quabqi
2021/11/04
6K0
UE4/UE5的TaskGraph
Unreal 骨骼动画源码剖析
其中,USkeletalMesh 是骨架网格体模型数据对象。USkinnedMeshComponent 支持了对骨架网格体的渲染,通过 FSkeletalMeshObject 将渲染所需数据发送到渲染线程,具体的渲染方式也由这个对象决定,例如使用 CPU 还是 GPU 进行渲染。 USkeletalMeshComponent 在此基础上支持了骨骼动画播放,具体动画播放逻辑由 UAnimInstance 实现。
zhiruili
2023/10/20
2.3K1
Unreal 骨骼动画源码剖析
个人塔防游戏Demo开发思路(UE4)
游戏为本人毕业设计,功能实现较为简陋,这里只是简要描述下开发思路,不包含深入的Gameplay框架分析,项目可无缝升级至UE4.26,素材全部来源于虚幻商城与互联网。完整项目下载,提取码:demo 游戏主体逻辑采用蓝图系统实现,支持多平台运行,包含存档功能,进入游戏后玩家可以在预先设置好的摆放位置购买和升级防御塔。游戏共有5波敌人,包括最终的BOSS关卡。在击败BOSS通关后即可进入无尽模式,此时游戏难度会不断提高,直到游戏结束。
LonelyEnderman
2023/08/09
1.3K0
个人塔防游戏Demo开发思路(UE4)
UE引擎里头跑个nodejs服务器是怎样一种体验?
不像python、lua、java等语言有个专门的、独立的可执行程序,js虚拟机更多的时候是嵌入到某个宿主里头,比如浏览器、nodejs。js虚拟机实现了某个js标准(比如es5、es6),宿主能力也会通过一些api导出给js使用,比如浏览器的dom操作,nodejs的异步io等。
车雄生
2021/11/10
1.3K0
ue4 插件开发(ue4性能优化)
UE4引擎和UE4项目是由各个模块组成的,其主要编程语言是C++。 插件也是一个模块,说到插件必须讲到耦合性与独立性,耦合性是对模块间关联程度的度量,模块间关联越强,则耦合性强,独立性差。 庞大的UE4引擎各模块相互参差作用,所以想要读懂并拆解并非易事,而创建UE4游戏项目实则只引入了引擎的部分模块,再想想如果很多游戏项目都需要用到这一个自定义功能时,是不是每个项目都要开发一次这个功能,个人或者一家公司内部还好,只要把项目代码复制给另一个项目即可,但是全球这么多的UE4开发者怎么办,开发者的结晶势必要发挥它最大的用处,把自己的研发成果分享出去,为了解决这类问题,就要使得模块独立起来,那么UE4插件就该闪亮登场了。
全栈程序员站长
2022/07/29
3.6K0
ue4 插件开发(ue4性能优化)
UE4 Editor/PIE比移动端卡顿的可能原因
这篇文章不长篇分析代码了,因为部分工作和想法由于时间、成本关系我也没完整验证,开个短文章讨论,或许有相同问题解决经验的朋友也能带来一些想法(之前也确实听其它公司的同学聊过一些他们遇到的问题)。
Bairuo
2022/11/18
1.1K0
UE4 Editor/PIE比移动端卡顿的可能原因
【技术总结】UE4中的Subsystem
在游戏开发过程中我们往往需要创建一系列的工具来辅助我们开发,例如UI管理工具,各类导表工具。在UE4.22之前我们只能够自己编写单例,并且自己管理生命周期。或者直接将管理游戏的工具编写进GameInstance中。但是随着代码量的增加,GameInstance将会变得难以维护。在4.22版本发布了之后,我们可以直接将工具写在Subsystem中,让引擎帮我们自动管理工具类的生命周期,不再需要自己维护工具的生命周期或者修改引擎的类(如GameInstance)。
太阳影的社区
2021/10/15
6.2K0
相关推荐
UE5的ECS:MASS框架(二)
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档