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的效率不能提升太多。

基于UE4的多RHI线程实现相关推荐

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

    在最前面 基于UE的手游客户端的性能主要由这七大部分构成:CPU逻辑,CPU渲染,图形API(提交),GPU渲染,内存,带宽,加载时间.这几个基本元素又会合力衍生出一些新的性能指标,例如功耗(往往同g ...

  2. UE4异步编程专题 - 线程池FQueuedThreadPool

    1. FQueuedThreadPool & IQueuedWork FQueuedThreadPool是UE4中抽象出的线程池.线程池由若干个Worker线程,和一个同步队列构成.UE4把同 ...

  3. 基于应用层自身反远程线程注入的研究

    基于应用层自身反远程线程注入的研究 现状:目前所有已知的反远程注入方式:r0层hook 句柄的获取,返回失败,让应用层注入者拿不到目标进程的句柄,如hook ntopenprocess ntdubli ...

  4. 【Flink】Flink 基于 MailBox 实现的 StreamTask 线程模型

    1.概述 转载:Flink 基于 MailBox 实现的 StreamTask 线程模型 先来给介绍一下目前 StreamTask 中基于 MailBox 实现的线程模型,这个模型从 1.9 开始实现 ...

  5. 基于UE4 的AirSim虚拟仿真

    一.前言 最近在研究UE4无人机驾驶的时候,用到了AirSim,研究了一下,发现配置的过程中让人很头疼,接下来就交大家怎么去配置基于UE4 的AirSim的环境. 二.环境准备 在搭建AirSim的环 ...

  6. 用Linux / C实现基于自动扩/减容线程池+epoll反应堆检测沉寂用户模型的服务器框架(含源码)

    用Linux/ C实现基于自动扩/减容线程池+epoll反应堆模型的服务器框架 前言 服务器端源码 客户端源码 自定义库 helper.c 和 helper.h helper.c helper.h M ...

  7. 基于UE4/Unity绘制地图 - 确定展示区域

    前言 基于UE4/Unity绘制地图基础元素-线 基于UE4/Unity绘制地图基础元素-面和体 基础知识 在研究清楚如何绘制地图的线面体之后,接下来需要确定需要展示的地图区域了. 地图可以看成是一个 ...

  8. Houdini 过程化地形系统(二):基于UE4的FC5植被系统(1)

    背景 通过之前的几篇分析实践,已经基本打通了UE4的Houdini植被管线部分,并对Far Cry5(简称FC5)的植被系统的需求做了整理,在接下来的几节中,会关注于如何使用Houdini基于UE4来 ...

  9. 一种基于UE4平台多人交互3D家装设计系统及使用方法与流程

    本发明涉及一种基于UE4平台多人交互3D家装设计系统及使用方法,属虚拟现实技术领域. 背景技术: 目前随着虚拟现实技术的飞速发展,当前在进行家装实际工作中,为了提高设计方案对用户展示的便捷性和准确性, ...

最新文章

  1. 2017-1-7 html元素分类(1)
  2. 好消息,Blackberry开发文档发布。
  3. WPF中的动画——(六)演示图板
  4. 26计算限制的异步操作01-CLR
  5. tf.nn.dropout
  6. python 导入的nan怎么解决_用Python处理了数据还要导入Excel做图表?直接Python做漂亮图表...
  7. x264代码剖析(四):vs2010编译x264错误集锦
  8. 用java怎么让时间走动起来,java脚本实现时间刷新
  9. (附源码)APP+springboot订餐APP 毕业设计 190711
  10. android expandablelistview简单应用,android ExpandableListView简单例子
  11. java poi word 复制_java poi实现word导出(包括word模板的使用、复制表格、复制行、插入图片的使用)...
  12. ClickHouse数据库介绍
  13. 域名备案方法及是否需要备案
  14. DNS协议解析与DNS模拟服务器-基于golang实现
  15. 计算机存储程序的理论由谁提出,存储程序的概念是由谁提出来的
  16. MFC应用程序框架-文档/视结构
  17. delphi使用SQL的教程7
  18. 写代码只是皮毛,这才是计算机科学
  19. win10磁盘占用100% 解决最重要的一个步骤
  20. 调光调色LED台灯触摸芯片-DLT8MA12T

热门文章

  1. 古摄影婚纱网站jQuery大屏幕广告插件
  2. 爬虫之获取当当网全部图书
  3. sign (数学符号函数)
  4. spring boot 单元测试报错 ,nacos discover未注册进服务中心
  5. 想创业先问自己20个问题
  6. ORACLE导出语句
  7. 如何进行有效的产品规划?
  8. 同花顺ifind和wind终端哪个做数据API接口更好
  9. Unity3D 地形的添加 和树、风、雾的基本知识
  10. chatgpt赋能python:Python如何发送消息到微信群?一步步教你实现