jquave 发表于 2022-3-31 15:11

【UE】UE的架构


不要过于在意他人的成败得失,也无需过于计较风与水的顺逆,那都是我们无法控制且对于我们自身的强大毫无帮助的无关扰动,我们真正应该关注的是如何才能让自己得到更快更好的成长,凡事以此为依归,才能更快体会到绝巅众山小的风光,Stay hungry, Stay foolish.

这里尝试根据平时学习过程中了解到的只鳞片羽来对UE的架构进行总结,以备不时之需。
1. 线程模型(UE4)

先来看下UE4的线程模型的概览图:

出于叙述方便,我们先来看下Game线程与Render线程:

从时序上来说,是Game线程先行,Render线程拿到的是之前的Game线程数据(比如延迟一帧之类,不过从图中绘制的对齐结果来看,并没有差到一帧这么大),Game线程到Render线程的跨线程调用是通过ENQEUE_RENDER_COMMAND实现的。

关于Render线程,我们还可以再深入一下:

Render线程中,相关的绘制逻辑是通过Rendering Command实现的,而Rendering Command最终要转化为D3D11等API的Command才能真正完成绘制,D3D11 Command的执行顺序是保序的,且与Rendering Command是交织进行的,参考上图中的蓝绿色块,蓝色表示Rendering Command,绿色表示D3D11 Command。

实际上,上面给出的是简化模型,在UE中还有一个RHI线程,负责完成Rendering Command到D3D11 Command的衔接与适配,具体的实现逻辑应该如下图所示:

Rendering Command进入到RHICmdList,经过RHI转换成对应API的调用指令,虽然上面绘制的示意图中看起来D3D11 Command的A(绿色A)绘制在Rendering Command的D(蓝色D)指令之后,但实际上并不是这样,两者应该是有重叠的,即RHICmdList并不是将所有的Rendering Command都搜集起来,之后串行转换为D3D11的Command,而是边搜集边转换的,如下图所示,有点类似于生产消费者模型(这个图看起来怪怪的,其实如果将RHICmdList放在Rendering Thread之下RHI Thread之上会好一些):

因为RHICmdList专门用于负责Rendering Command到图形API Command的转换,因此自然就有一种是否可以将这个功能单独分拆到一个线程的想法,如下图所示,分拆的好处是什么呢?

文献中给出的理由是:

游戏线程负责场景的更新,比如物理、动画等计算(如果场景中蒙皮物件较多,或者物理计算较为复杂,那么游戏线程就会承压);原始的渲染线程负责对场景进行遍历,计算每个物件的可见性,并将可见的物件的渲染指令提交到GPU上,后面UE增加了Task System,就将渲染线程中的遍历部分扔给了异步Task,剩下的两项还放在渲染线程中,不过实际上渲染线程也可以设置成多线程并行完成的。

D3D11在多Render线程Command提交在效率上不如将所有Command交给一个线程来提交(而OpenGL ES则限定了Graphic API跟GLContext必须处在同一个线程上,直接杜绝了多Render线程的提交),为了应对这种问题将Render线程一分为二,分为Render线程跟RHI线程,拆分后的Render线程负责拆分前的Render线程的Visibility计算逻辑部分,而RHI线程则负责拆分前的Render线程的Command Submit逻辑,拆分开来之后,新的Render线程可以同时开启多个,通过多核CPU的并行能力来减少CPU计算的时间消耗。

因为D3D11渲染线程提交能力提升不上去,因此导致的一个直接结果就是GPU长期处于低负载运行状态,相当于硬件能力没有较好发挥出来。因此一些较为新颖的特性比如RTX或者VRS等,通常都建议在DX12上才能开启。

不过这种做法是一个临时方案,因为更新版本的API(比如Vulkan或者D3D12),已经能够很好的支持多线程的提交,其效率不再如D3D11一样不如单线程提交了,因此后面RHI线程的作用会逐渐废弃,转为使用此前的Game-Render线程模型,不过Render线程通常会包含众多的线程,每个线程的功能也重新恢复原有的Visibility计算与Command Submit两部分。

我的理解,应该是还有其他的考虑,比如将渲染指令从CPU提交到GPU,可能会有阻塞,在这种情况下,如果执意使用Render Thread进行提交,就会导致Render Thread的阻塞,但实际上,我们可将阻塞放到RHI Thread上,Render Thread可以继续干自己想干的事情,可以实现部分工作的并行化,这样执行效率应该会高一些。

在多个Render线程提交的情况下,线程模型示意图给出如下:


转换到RHI线程模型,大概如下图所示:

参考文献

Scalability for All: Unreal Engine* 4 with Intel
Unreal Engine中的RHI线程
Epic Games 王祢:UE4制作多人大地型游戏的优化
页: [1]
查看完整版本: 【UE】UE的架构