《图解UE4渲染体系》Part 1 多线程渲染

《图解UE4渲染体系》Part 1 多线程渲染

上回书《Part 0 引擎基础》说到,我们粗略地知道UE4是以哪些类来管理一个游戏场景里的数据的,但这仅仅是我们开始探索UE4渲染体系的一小步。

本回主要介绍UE4渲染体系中比较宏观顶层的一部分——多线程渲染,具体的多线程中,又分为:

  • 游戏线程(GameThread)
  • 渲染线程(RenderThread)
  • RHI线程(Render Hardware Interface Thread)

《图解UE4渲染体系》Part 1 多线程渲染

为什么是多线程?

《图解UE4渲染体系》Part 1 多线程渲染

用来描述“渲染”的最基础的理论就是像图上的那样,CPU调用图形API提供的DrawCall命令(也叫绘制指令),在命令中说明需要渲染的数据、属性等等,然后CPU等待GPU返回渲染结果,完成渲染。对于那些渲染频率不高的场景,这种方式并没有什么问题,但在游戏这种需要实时性渲染的高频率场景下,问题就显现出来了。

游戏引擎完成渲染不只有提交DrawCall这一个任务,除了这个以外,CPU要花费非常多的时间在处理游戏逻辑运算和准备渲染数据上,比如处理用户的输入、执行游戏脚本、更新物理和动画、可见性剔除等等等等。

《图解UE4渲染体系》Part 1 多线程渲染

假如引擎把所有的事都交由GameThread来完成,当GameThread把当前这一帧该做的事都做完了,准备好要渲染的数据,提交到GPU后,GameThread就只能等待渲染结果,但GameThread接受到当前这一帧的用户输入后,完全可以去执行下一帧的各种任务,但单线程的机制并不允许这样的事情。

多核心的CPU和多线程并发并行的操作系统在今天已经不是什么稀罕事了,将与渲染相关的任务从GameThread中剥离出来,让GameThread专注处理游戏逻辑上的的各种计算任务,让RenderThread专门和GPU来完成渲染任务,就成了自然而然的事情。

《图解UE4渲染体系》Part 1 多线程渲染

加入RenderThread后,每次GameThread处理完各种任务,准备好渲染数据,把数据发送给RenderThread,然后就继续处理下一帧的任务了,RenderThread收到数据,进行一些数据处理后(比如可见性剔除),向GPU提交DrawCall,等待渲染结果,完成渲染。

那RHIThread是什么呢?UE4中RHI的提出可能有很多原因:

  • 支持跨平台多种图形API
  • 并行提交DrawCall
  • 其他各种各样的性能优化

首先是针对跨平台多种图形API,由于不同平台支持的图形API不同,Windows限定的Direct3D、MacOS限定的Metal以及跨平台(包括移动端)的OpenGL和Vulkan,在有RHIThread之前,RenderThread会根据不同的图形API来选择DrawCall,这肯定会增加不少工作量,维护也更加复杂。

"All problems in computer science can be solved by another level of indirection." —— Jay Black

如果把这件事交由单独的一个线程来做,岂不美哉?这不,RHIThread就来了。

《图解UE4渲染体系》Part 1 多线程渲染

RenderThread准备好渲染数据后,向RHIThread提交一个与图形API无关的RHIDrawCommand,RHIThread掏出来一个表,查找当前平台的图形API里哪一句是对应的DrawCall,然后再向GPU提交DrawCall,等待渲染结果,完成渲染。这样一来,RenderThread就可以在自己的任务上专注(方便优化),在RHIThread上完成对各个平台的图形API版本迭代维护。

《图解UE4渲染体系》Part 1 多线程渲染

当然这是从工程优化角度上RHIThread存在的理由,当然RHI还有一些更加直接的存在理由,那就是为了支持并行化提交DrawCall。在一些比较旧的图形API里,DrawCall都是阻塞的,即一个线程提交DrawCall时,不允许其他线程提交。图形API调用GPU计算后,GPU本身计算渲染是需要时间的,而在这时间里,图形API如果能准备好下一次DrawCall,那必然是更好的。

《图解UE4渲染体系》Part 1 多线程渲染

随着技术更新,一些新的图形API开始提供一些并行化提交DrawCall的方式,在没有RHI的时候,难道让UE4跑多个RenderThread吗?好像也不太合理,RenderThread里面除了提交DrawCall的其他部分也不需要多个线程来完成,那需要单独提出来多线程化的任务就顺理成章地变成了RHIThread了。

总结

可以看到UE4渲染体系中多线程渲染的设计并不是一开始就是这样,而是跟随着技术的需求在不断发展进步的(新的UE5里面估计又改了不少了)。

本回并没有着重讨论各种线程内部细节的任务,也没有非常深入的讲解各个线程之间是如何传递具体的命令和数据的,因为讲起来那篇幅真的就太长了,之后再慢慢地整理吧,网络上的资料也很多,大家可以自行拓展阅读。

参考文献

发表评论

相关文章