基于UE4的多RHI线程实现

AI权益加码!Claude Code、Cursor等20+工具免费用! 购周边限时加赠Coding Plan Lite,畅享主流AI工具!学习进阶更高效! 阅读详情

1 UE4的现有多线程架构

UE的多线程渲染结构如下图, 它有几个特点

  • game thread 负责逻辑tick,render thread 负责culling、batching和draw api的生成, rhithread 负责drawapi的执行
  • game和render两个线程最多可以差一帧,即前后两帧的gam额和render可以并发,通过Fframeendsync做同步
  • render 和rhi之间的关系是帧内的合作关系,即不能达到前后两帧render和rhi的并发,前一帧的drawapi最多在下一帧的culling完成时完成。
    请添加图片描述

2 为什么需要更多的RHI线程

在相当多的情况下,RHI线程仍然会成为瓶颈,例如更复杂的场景,伴随更多的渲染状态切换同更多的drawcall,RHI线程的瓶颈典型的有以下两种情况:

大量dracall

这种情况存在大量的api call调用,通常是场景的物件量实在过大,如下图,它的瓶颈实际上发生在cpu driver对api command的serilize到cmd buffer的过程中,因为单个rhi线程只拥有单个cmdbuffer的serialize能力,所以造成瓶颈。
请添加图片描述
对这种情况,可以优化的方向是基于vulkan/METAL等先进api,他们具有多个command buffer,开启多个rhi线程,在多个rhi线程中并行serrielize api commmands。
请添加图片描述
如果是gles平台,这样做则意义不大,因为gles只能提供一个并发的command buffer供serialize

heavy api call瓶颈

有些api call的开销是巨大的,它通常需要在cpu的driver内进行大量的数据操作,这种api本文称为heavy api,以gles为例,典型如:

  • glBufferData()
  • glBufferSubData()
  • glCompressedTexImage2D() and glCompressedTexImage3D()
  • glCompressedTexSubImage2D() and glCompressedTexSubImage3D()
  • glTexImage2D() and glTexImage3D()
  • glTexSubImage2D() and glTexSubImage3D()
  • glcompileshader and glshadersource
  • gllinkprogram

这些大多为资源准备型的api call,在很多android机上一次glcompileshader调用可能要上百ms,相比之下通常的gldrawprimitive则开销很小,通常在0.1ms以下,heavy api call的瓶颈如下图
请添加图片描述
其中红色为heavy api,而绿色的为drawprimitve api。可以看到heavy api卡住了正常drawprimitve api的输送,瓶颈不在“serielize”而是在cpu侧driver对api的处理。

对于这种情况,可以考虑的优化是将heavy api同drawprimitive api分离,使用单独的线程处理heavy api,以使drawprimitive api更顺畅的被提交。如下图

请添加图片描述
在实际项目中,heavy api call带来的RHI线程瓶颈比单纯的大量drawcall本身要严重,即RHI通常是卡在对api的处理上,而不是对api的serielize上,所以我们常说drawcall多并不一定就会卡,drawcall多只是会更加容易触发渲染状态的改变,继而更加容易触发heavy api call的发生。
此外对heavy api call的优化在gles这种低端机使用的平台上也能奏效,更有实际意义。
所以在我的项目中,主要进行了“将heavy api call分离到单独rhi线程”这种多RHI线程优化。

3 “分离heavy API call”的多RHI线程的设计思想

3.1 总体架构

我们为UE增加了更多的RHI线程。UE原本的这个RHI线程本文称为主RHI线程,或“Draw RHI Thread”, 而新增的一个或多个RHI线程称做辅助RHI线程,或“Auxiliary RHI Thread”。我们将原本主rhi线程上的heavy api call分离出来放在auxiliary rhi thread上运行。整体的多线程渲染架构如图
请添加图片描述

auxiliary rhi上运行了很多heavy api call,为draw rhi准备好资源

3.2 多RHI线程间的关系和同步策略

这里最大的难点是将原有一条线上有时序关系的api command 队列的执行分布到了多条线上,同时要保证每个drawprimitive api发生时它所需要的资源已经在auxliary 线程上准备好。

api call之间存在三种依赖形式:

  • 1 drawprimitive call对资源准备 api call(buffer,shader,texture)的依赖
  • 2 资源准备api call对另一个资源的依赖(gllinkprogram需要依赖所使用的shader编译好)
  • 3 资源在cmd buffer中途的状态改变(如绘制原语a之前和绘制原语b之前的某个ub内容发生改变)
3.2.1 drawprimitive对资源(buffer shader texture)的依赖

一个最容易想到的策略是“资源预测式”,即我们能够提前预测到对资源的使用,保证在draw rhi thread处理到primtive之前,早已经在auxiliary rhi thread上处理好resouce,(例如在物件加载时就去处理RHI资源,赶在渲染前处理好),这种方式对代码的破坏巨大,需要在很多地方插入预处理资源rhi的代码。此外这种方式不够稳妥,某些资源如果在使用前没有处理好依然会卡住 draw rhi thread。

我们目前采用的方式是“no ready no use”,即resouce api还是在原有时机产生,只是发送到auxliary rhi thread 处理,在draw rhi thread上,使用这个resource前检查其是否ready,如果没有准备好,则后续的primitive取消绘制。这种方案的结果就是在资源准备好之前物件不显示,类似于streaming加载。
这种方式的机制如下图:
请添加图片描述

  • render thread产生的所有 rhi cmd要同时发送给draw和auxliary两个thread
  • draw thread 使用前只检查资源状态
  • auxilary thread真正处理资源
  • draw thread上的drawprimitive可能因为res没有准备好而被在当前帧取消绘制(延迟出现)
3.2.2 处理资源之间的依赖

使用一个全局的表记录每个资源当前的状态,例如pending/ processing/ waiting/ ready,当依赖的资源没有ready的情况下,这个资源的处理就会在线程上waiting
请添加图片描述
这种waiting会导致aux rhi thread的阻塞,影响效率,我们优化成下图,即跳过这个需要waiting的资源,将该cmd重新加入队尾,后面再执行
请添加图片描述

3.2.3 处理资源的状态改变

这种情形比较复杂,目前只在aux thread上处理静态资源改变,如shader的编译,static buffer的创建等。

4 其他细节

Gles上实现多线程访问

在gles上,可以使用多个线程访问gles资源,但是需要遵从一些规范:

  • 需要为每个rhi线程创建单独的egldrawsurface,eglreadsurface和eglcontex,并在该rhi线程上调用eglmakecurrent将egldrawsurface,eglreadsurface和eglcontex三者进行绑定,创建durface和context的代码补充在了AndroidEGL::InitContexts()和AndroidEGL::CreateEGLSurface()中,对三者进行绑定的代码在void AndroidEGL::SetAuxRenderingContenxt(uint32 AuxIndex)中

  • 如果a线程需要访问b线程创建的gl resource,那么a线程的eglcontext必须是b线程的eglcontext的parent,只有单向的线程间资源访问,这意味着只有drawrhithread能够访问au rhi thread创建的资源,反之不能,draw rhi的eglcontext需要设置为所有aux rhi的eglcontext的parent

5 其他问题

使用多少个Auxiliary RHI Thread是合适的?

这取决于运行机器有多少个小核,为了不影响功耗,我们将Auxiliary RHI Thread绑定在小核上运行,超过小核数量的线程数意义不大,主流android机一般至少有4个小核,所以最多开启4个aux rhi thread是没问题的

为什么在gles上实际shader和program的编译都放在了同一个aux thread上?

在测试中发现,在主流机器的gles实现上,即使是不同的shader的编译,在多个线程上依然会存在较大的互斥区发生线程间的等待,这个情况同样发生在shader的编译和program的link上,所以使用多个aux thread的效率不能提升太多。

虚幻引擎之多线程渲染机制 虚幻引擎之多线程渲染机制 文章目录虚幻引擎之多线程渲染机制一、前言二、游戏线程渲染线程的交互2.1 ENQUEUE_RENDER_COMMAND宏2.2 渲染线程2.3 数据交互2.3.1 数据更新2.3.2 创建渲染器2.3.3 触发渲染管线2.4 同步三、渲染线程RHI线程3.1 RHI线程的创建3.2 RHI的任务参考文章 一、前言 在 虚幻引擎编程基础(二) 中,笔者简单地介绍了虚幻引擎中的多线程的类型,共分为三类: 标准多线程实现FRunnable; 使用线程池的AsyncTask; Tas 阅读详情

相关推荐

UE4 多线程的使用

在现代操作系统中,多线程是一项极其重要的特性。因此基于多线程平台开发出来的程序应该要好好的利用这种特性。虚幻引擎作为跨平台的、大型游戏引擎,也提供了一套自己的使用平台多线程的解决方案,首先来了解一下虚幻多线程的基本概念。虚幻多线程分为专用线程线程线程,专用线程——游戏线程(GameThread)、渲染指令生成线程()、渲染指令执行线程RHIThread)、音视频处理线程()等;

m0_53295313的博客 1213

UE高级性能剖析技术(1)-- RHI线程渲染提交)

在最前面 基于UE的手游客户端的性能主要由这七大部分构成:CPU逻辑,CPU渲染,图形API(提交),GPU渲染,内存,带宽,加载时间。这几个基本元素又会合力衍生出一些新的性能指标,例如功耗(往往同gpu负载和带宽紧密相关)。同时这七部分又构成一个闭合的木桶,最长的一块是主要瓶颈,并且瓶颈可以在这几块转移流动。作为开发者我们解决性能问题的步骤一般都是按照做性能剖析,解读结果,定位问题,增加剖析代...

leon 1万+

UE4 RHI浅析

RHI: Render hardware interface 渲染硬件层接口, 本人理解RHI是一套硬件无关,平台无关的图形渲染API.  它是如何做到与平台无关,与硬件无关的呢? 每个RHI接口都对应着个图形API的实现版本. 对于每个RHI接口,其实都有针对DX11,DX12,OpenGL等版本的实现。对于不同平台,引擎初始化的时候就已经确定要用哪一套图形API了。 之后,调

tiantian至简的专栏 9642

手机游戏性能优化:RHI线程与大核数量

移动设备CPU架构对UE引擎RHI线程性能影响显著。高端机型(如1+3+4架构)开启RHI线程能有效分担渲染压力,提升帧率;而中低端机型(2+6架构)因大核资源有限,RHI线程可能被调度到小核,导致线程竞争和性能下降。建议根据CPU大核数量动态决定是否启用RHI线程:大核≥3时开启,否则关闭。该策略能优化核利用率,平衡不同硬件条件下的渲染性能。

本博客聚焦游戏开发技巧、创业实战心得及大学热门课程干货。这里既有技术深度,也有思维广度,无论你是开发者、创业者还是高校学子,都能在这里找到适合自己的成长之路! 1293

从源码深入理解Unreal渲染管线

最近在搞超分相关的工作,发现自己对Unreal渲染管线的理解还是太浅,于是边工作边整理了此文,整篇文章比较长,建议边打开Unreal源码边阅读,跟随文中的顺序能够将Unreal渲染管线错综复杂的源码模块化串联起来,以后有新的发现也会持续更新此文,如果对文章内容有疑问欢迎指正。

hacning的博客 7766

UE4】虚幻引擎运行流程

UE4】虚幻引擎运行流程

ttod的博客 3810

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

上回书《Part 0 引擎基础》说到,我们粗略地知道UE4是以哪些类来管理一个游戏场景里的数据的,但这仅仅是我们开始探索UE4渲染体系的一小步。 本回主要介绍UE4渲染体系中比较宏观顶层的一部分——多线程渲染,具体的多线程中,又分为: 游戏线程(GameThread) 渲染线程(RenderThread) RHI线程(Render Hardware Interface Thread) 为什么是多线程? 用来描述“渲染”的最基础的理论就是像图上的那样,CPU调用图形API提供的DrawCall命令(也

ACStudio 4249

从源码看UE渲染线程设计:为什么你的DrawCall总在RHI线程卡住?

本文深入解析了虚幻引擎(UE)渲染线程架构,从DrawCall到GPU提交的全链路优化策略。重点分析了多线程设计下RHI线程卡顿的常见原因,包括资源争用、命令提交模式等问题,并提供了六大优化策略如命令批处理、资源预创建等。通过性能分析工具和实战案例,帮助开发者提升渲染效率,解决DrawCall性能瓶颈。

weixin_28025327的博客 344

UE4渲染管线学习笔记

UE4渲染管线学习笔记

学无止境 9827

剖析虚幻渲染体系(10)- RHI

剖析虚幻渲染体系

ttod的博客 1467

UE4渲染流程

虚幻引擎在FEngineLoop::PreInit中对渲染线程进行初始化。 渲染线程的启动位于StartRenderingThread全局函数中。 创建渲染线程类实例 通过FRunnableThread::Create函数创建渲染线程 等待渲染线程准备好,从TaskGraph取出任务并执行 注册渲染线程 创建渲染线程心跳更新线程 渲染线程的主要执行在全局函数RenderingThreadMain中,游戏线程会借助EQUEUE_Render_COMMAND宏,向渲染线程的TaskMap中添加渲染

qq_33727884的博客 7861

腾讯游戏学院专家:UE高级性能剖析技术之RHI

https://www.fgba.net/sitemap.xml

august5291的博客 1135

UE4 渲染学习笔记(未完)

UE4 渲染笔记

asiwxy的博客 1421

UE4 Shader 编译以及变种实现

UE4 Shader 编译以及变种实现 https://zhuanlan.zhihu.com/p/154081604 一 , 动机 这篇文章主要是我对UE4中Shader编译过程以及变种的理解,了解这一块挺有必要的,毕竟动辄几千上万个Shader的编译在UE里简直是家常便饭,了解它底层的实现机制后内心踏实一点,要去修改的话大方向也不错 这部分工作是我之前就做好的,文章里涉及内部修改的地方都被我阉割掉了(阉割版,之后还有内容我再加。。 工作繁忙外加懒) 所以这篇文章主要用于知识普及,分享给广大被UE

kuangben2000的博客 4105

深入解析虚幻引擎多线程渲染的数据同步机制

本文深入探讨了虚幻引擎中多线程渲染的数据同步机制,详细解析了游戏线程渲染线程的协作基础、ENQUEUE_RENDER_COMMAND的工作原理以及高级同步控制模式。通过双缓冲数据架构和智能指针等技术,确保线程安全与性能优化,特别适合游戏开发者和图形程序员提升渲染效率。

weixin_29017445的博客 206

UE4源代码观察】观察D3D11是如何被RHI封装的

我想了解D3D11是如何被UE4RHI封装的。我知道这牵扯到很复杂的内容,但通过观察一些最基础的API被谁在哪调用,可以对RHI是如何封装的有一个大致了解。在之前的博客《创建一个最小的D3D11实例》中,有一些最基础的D3D11的API调用,我选他们作为观察的对象。 ID3D11Device 是怎么被创建的? 首先,FD3D11Device 被定义为了 ID3D11Device,这在 D3D1...

主要记录了工作之余胡乱学习的知识,大部分是入门级别的内容。 主要是为了自己未来有用到时能快速上手,相当于是个备忘录。 当然,也很开心其他人可以从中找到对自己有用的信息。^_^ 2987

UE4 Shader编译以及变种实现

一、动机 这篇文章主要是我对UE4中Shader编译过程以及变种的理解,了解这一块还是挺有必要的,毕竟动辄几千上万个Shader的编译在UE里简直是家常便饭。了解它底层的实现机制后内心踏实一点,如果要去修改,大方向也不会错。 这部分工作是我之前就做好的,文章里涉及内部修改的地方都被我阉割掉了。所以这篇文章主要用于知识普及,分享给广大被UE4中的Shader编译折磨的码农们,凑活着看,看完其实应该就了解了。 二、UE4中Shader的组织和获取 在讲具体的Shader编译过程时,先讲UE4渲染过程,

UWA—简单优化,优化简单! 3593

格格她爹讲程序---用传统程序员的方式玩UE4(三)

格格她爹讲程序---用传统程序员的方式玩UE4(三)   最近经常有人问我,什么时候出下一章,我觉得还是很高兴的,至少我写的东西得到了认可,但是我也要向大家说明一下,毕竟我现在还有工作,平时工作很忙,所以只能通过闲暇的时间来写,上次的两篇就是在出差的高铁上完成的。 上一章的一个典型应用其实只是为了让大家尽快出点“成绩”,好对UE4的代码开发有点兴趣,这次我们继续回到正规,先从UE4的基本渲染

tinyfog的专栏 1070

UE4面试基础知识(一)

1、Actor的EndPlay事件在哪些时候会调用? EndPlay - 在数个地方调用,保证 Actor 的生命走向终点。在游戏过程中,如包含流关卡的 Actor 被卸载,Destroy 将发射此项和关卡过渡。调用 EndPlay 的全部情形: (1)对 Destroy 显式调用 (2)Play in Editor 终结 (3)关卡过渡(无缝行程或加载地图) 包含 Actor 的流关卡被卸载 (4)Actor 的生命期已过 (5)应用程序关闭(全部 Actor 被销毁)

永远的小白虾的博客 3万+
上一篇: UE高级性能剖析技术(三)-- Android内存分布和优化
下一篇: Vulkan下多线程渲染设计
leonwei
leonwei 领域专家: 游戏开发技术领域 领域专家: 游戏开发技术领域
博客等级 码龄19年 4371粉丝 125原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值