UE-RHI (Render Hardware Interface渲染硬件接口)

本文摘抄自深度剖析虚幻渲染体系-RHI

  • 封装了众多图形API(DirectX、OpenGL、Vulkan、Metal)之间的差异,对Game和Renderer模块提供了简便且一致的概念、数据、资源和接口
  • RHI线程,它负责将渲染线程Push进来的RHI中间指令转译到对应图形平台的GPU指令
  • 最初的RHI是基于D3D11=DirectX11 API设计而成,包含了资源管理和命令接口

二-RHI基础

RHI资源相关的类

(1)FRenderResource

  • 资源运送的流程:游戏线程->渲染线程->RHI线程

  • FRenderResource是渲染线程的渲染资源代表,由渲染线程管理和传递,介于游戏线程和RHI线程的中间数据。

  • 在渲染线程中,FRenderResource 定义了一组渲染资源的行为,FRenderResource的子类就是对应地将RHI的子类资源封装起来,以便渲染线程将游戏线程的数据和操作传递到RHI线程(或模块)中。

(2)FRHIResource

(类继承架构:FRHIResource-> RHI中的各个子类->图形API中的对应子类)

  • FRHIResource抽象了GPU侧的资源,也是众多RHI资源类型的父类。
  • FRHIResource提供了几种功能:引用计数、延迟删除及追踪、运行时数据和标记。
  • FRHIResource的种类和子类都非常多,可分为状态块、着色器绑定、着色器、管线状态、缓冲区、纹理、视图以及其它杂项。
  • FRHIResource的子类会被图形API中的子类对应继承实现API的封装。

RHI命令相关的类

(3)FRHICommand

  • FRHICommand是RHI模块的渲染指令基类,这些指令通常由渲染线程通过命令队列Push到RHI线程,在合适的时机由RHI线程执行。
  • FRHICommand同时又继承自FRHICommandBase,FRHICommandBase有指向下一个节点的Next变量,意味着FRHICommandBase是命令链表的节点。

(4)FRHICommandList

(类继承架构:FRHICommandListBase-> FRHIComputeCommandList->FRHICommandList->FRHIComputeCommandList)

  • FRHICommandListBase定义了命令队列所需的基本数据(命令列表、设备上下文)和接口(命令的刷新、等待、入队、派发等,内存分配)。
  • FRHIComputeCommandList定义了计算着色器相关的接口、GPU资源状态转换和着色器部分参数的设置。
  • FRHICommandList是RHI的指令队列,用来管理、执行一组FRHICommand的对象。FRHICommandList定义了普通渲染管线的接口,包含VS、PS、GS的绑定,图元绘制,更多着色器参数的设置和资源状态转换,资源创建、更新和等待等等。
  • FRHICommandListImmediate封装了立即模式的图形API接口,在UE渲染体系中被应用得非常广泛。它额外定义了资源的操作、创建、更新、读取和状态转换接口,也增加了线程同步和GPU同步的接口。

三-RHIContext, DynamicRHI

目测讲述的是RHI和图形API之间的上下文,还有通过DynamicRHI让图形API的接口动态绑定

(1)IRHICommandContext

  • IRHICommandContext是RHI的命令上下文接口类,定义了一组图形API相关的操作。IRHICommandContext的接口和FRHICommandList的接口高度相似且重叠

(2)IRHICommandContextContainer

  • IRHICommandContextContainer就是包含了IRHICommandContext对象的类型,相当于存储了一个或一组命令上下文的容器,以支持并行化地提交命令队列,只在D3D12、Metal、Vulkan等现代图形API中有实现。

(3)FDynamicRHI

  • FDynamicRHI是由动态绑定的RHI实现的接口,它定义的接口和CommandList、CommandContext比较相似

(4)RHI 体系总览

  • 上文详细阐述了RHI体系下的基础概念和继承体系,包含渲染层的资源、RHI层的资源、命令、上下文和动态RHI

四-RHI机制

(1)RHI命令执行

(类继承架构:FRHICommandListExecutor-> GRHICommandList)

FRHICommandListExecutor
  • FRHICommandListExecutor负责将Renderer层的RHI中间指令转译(或直接调用)到目标平台的图形API
  • FRHICommandListExecutor处理了复杂的各类任务,并且要判定任务的前序、等待、依赖关系,还有各个线程之间的依赖和等待关系。
  • 代码中涉及到了两个重要的任务类型:派发RHI线程任务和执行RHI线程任务
GRHICommandList

(2)ImmediateFlush

描述了不同的刷新策略,通过枚举变量进行区分。

(3)并行渲染

FParallelCommandListSet

并行渲染队列存放并行渲染的命令集合

(4)Pass渲染

普通Pass渲染
Subpass渲染
  • 移动端渲染策略:上一个Pass渲染处理的纹理,立即被下一个Pass使用,并且下一个Pass只采样像素位置自身的数据,而不需要采样邻域像素的位置。这种情况就符合了Subpass的使用情景。使用Subpass渲染的纹理结果只会存储在Tile Memory中,在Subpass结束后不会写回VRAM,而直接提供Tile Memory的数据给下一个Subpass采样读取。这样就避免了传统Pass结束写回GPU显存以及下一个Pass又从GPU显存读数据的耗时耗电操作,从而提升了性能。

(5)RHI资源管理(见博客原文)

(5)多线程渲染

  • UE的渲染流程中,最多存在4种工作线程:游戏线程(Game Thread)、渲染线程(Render Thread)、RHI线程和GPU(含驱动)。

  • 游戏线程是整个引擎的驱动者,提供所有的源数据和事件,以驱动渲染线程和RHI线程。游戏线程领先渲染线程不超过1帧,更具体地说如果第N帧的渲染线程在第N+1帧的游戏线程的Tick结束时还没有完成,那么游戏线程会被渲染线程卡住。反之,如果游戏线程负载过重,没能及时发送事件和数据给渲染线程,也会导致渲染线程卡住。

  • 渲染线程负责产生RHI的中间命令,在适当的时机派发、刷新指令到RHI线程。因此,渲染线程的卡顿也可能导致RHI的卡顿。

  • RHI线程负责派发(可选)、转译、提交指令,且渲染的最后一步需要SwapBuffer,这一步需要等待GPU完成渲染工作。因此,渲染GPU的繁忙也会导致RHI线程的卡顿。

  • 除了游戏线程,渲染线程、RHI线程和GPU的工作都是存在间隙的,即游戏线程提供给渲染任务的时机会影响渲染工作的密度,也会影响到渲染的时间,小量多次会浪费渲染效率。