UE4中的RHI指的是Render hardware interface,作用像Ogre里的RenderSystem,针对Dx11,Dx12,Opengl等等平台抽象出相同的接口,我们能方便能使用相同接口对应不同渲染平台.
和以前一样,先简单介绍一些类与文件的作用,我们有个抽象的了解.
RHI.h :主要定义一些硬件平台的公共变量.
一是 硬件支持项,如是否支持PF_FloatRGBA格式渲染目标,手机平台是否支持FrameBuffer拾取,支持体纹理,支持硬件合并渲染等等.
二是 硬件变量,如最大Cube纹理数,阴影贴图长宽最大值等等.
三是 常见渲染定义,如FSamplerStateInitializerRHI纹理采样,FRasterizerStateInitializerRHI栅栏化(填充格式,正方向定义,MSAA),FDepthStencilStateInitializerRHI逐片断处理中的模板与深度,FBlendStateInitializerRHI逐片断处理中的混合.FRHIDrawIndirectParameters/FRHIDrawIndexedIndirectParameters DrawCall中相关参数.
DynamicRHI.h :包含FDynamicRHI接口定义,渲染所需求所有接口,创建buffer,创建纹理,设置着色器参数,UAV等,简单来说,对应opengl,dx提供的渲染API,其DynamicRHI.cpp文件会根据平台(Windows,apple,android等等)来选择加载合适的渲染平台(如Opengl,Dx,Vulkan等),在RHI模块的private文件夹下,可能看到各个系统会如何选择相应的渲染平台.
FRenderResource:定义接口如InitDynamicRHI /ReleaseDynamicRHI /InitRHI /ReleaseRHI /InitResource /ReleaseResource /UpdateRHI等渲染资源选择实现函数.
RHICommandList相关文件是我们讲RHI主要需要讲的,在这我们先来分析出现的各个类.
FRHICommandBase: 主要定义一个函数指针,一个执行方法调用函数指针指向的函数。
函数指针:二个参数(FRHICommandListBase,FRHICommandBase)CallExecuteAndDestruct:传入自己FRHICommandBase到时函数指针指向的方向.
FRHICommand: FRHICommandBase的一个模板子类,模板需要定义Execute方法,其方法只需要FRHICommandListBase,其会退化上面CallExecuteAndDestruct的FRHICommandBase参数,默认为自己.
FRHICommand的模板具体化,对应SetRasterizerState/SetDepthStencilState/SetShaderParameter等等,几乎所有渲染API都有对应的FRHICommand的模板具体化实现.
FRHICommandListBase: 相应FRHICommandBase的链表实现,以及定义一些上下文如IRHICommandContext ,IRHIComputeContext ,并且有相关和RHI线程交互的API,RHI本身相应的FRHICommandBase与List都是存放在渲染线程中,RHI线程可以用于在渲染线程中同步执行异步的复杂操作,如压入很多FRHICommandBase到渲染线程中执行,有些操作可以放入RHI线程中与渲染线程一起执行,在某段FRHICommandBase前,调用WaitForTasks等同步渲染线程与RHI线程,大家可以这么理解,RHI线程对于渲染线程就相当于渲染线程与游戏线程的关系,大家可以看我上篇UE4里的 渲染线程 ,看到如何在渲染线程里压入RHI线程,如何用WaitForTasks与渲染线程同步等.
FRHICommandList: 简单来说,所有用于渲染API几乎都有二种方法,一种是插入FRHICommandListBase链表,一种是直接调用相应渲染平台对应FDynamicRHI的实现,在这说下,我看了下OpenGLDrv相应的FDynamicRHI实现,相应API如SetShaderParameter, SetDepthStencilState等等,并没有直接调用相应的OpenGL的API,而是把相关改动放入一个FOpenGLRHIState的结构中保存起来,等到DrawCall(如RHIDrawPrimitiveIndirect等)相关命令调用后,才把各个改动对应opengl的API调用起来,如上的glProgramUniform等.
FRHIAsyncComputeCommandList: 多GPU的FRHICommandList实现。
FRHICommandListImmediate: 直接调用相应渲染平台对应FDynamicRHI的实现,对比FRHICommandList,主要是创建资源这一块的FDynamicRHI封装,可以看到它的一些函数都是以Create开头的。
FRHICommandListExecutor: 简单来说,管理FRHICommandListBase的几个子类单例实现,方便查找到如上的FRHICommandListImmediate 与FRHIAsyncComputeCommandListImmediate 单例实现,一般我们看到渲染代码里常见的如FRHICommandList/RHICmdList就是指的是FRHICommandListExecutor::GetImmediateCommandList().
在这,关于RHI的就先简单了解下,RHI主要调用都在渲染线程中,不过也可以使用FRHICommandListBase链表与RHI线程来实现一些同步异步操作。其中渲染模块中FRHICommandList/RHICmdList一般是FRHICommandListExecutor::GetImmediateCommandList(),这个是直接调用相关FDynamicRHI实现,一般并不与RHI线程交互。
介绍RHI模块后,我们来看下渲染模块的相关实现,在说下渲染模块的实现前,简单说下,UE4中大量用到C++ 的模版,除开自动生成各个分支代码,还有二点,一是代替部分接口类,减少如虚函数表的性能,二是减少一些分支判断,还是提高性能。但是会造成阅读代码比C#等语言验证,主要在于有些模板你都不知道是那些类可以用等,还好,UE4里一般这种模板使用类都有相同的前缀或是后缀,我们可以记一些相同的前缀或后缀转化成自己认为的接口实现。
我们先看一段代码,是OpenGLDrv实现的FDynamicRHI子类FOpenGLDynamicRHI的RHIDrawPrimitiveIndirect,简接绘制多组图元集。
void FOpenGLDynamicRHI::RHIDrawPrimitiveIndirect(uint32 PrimitiveType,FVertexBufferRHIParamRef ArgumentBufferRHI,uint32 ArgumentOffset) { if (FOpenGL::SupportsDrawIndirect()) { VERIFY_GL_SCOPE(); check(ArgumentBufferRHI); GPUProfilingData.RegisterGPUWork(0); FOpenGLContextState& ContextState = GetContextStateForCurrentContext(); BindPendingFramebuffer(ContextState); SetPendingBlendStateForActiveRenderTargets(ContextState); UpdateViewportInOpenGLContext(ContextState); UpdateScissorRectInOpenGLContext(ContextState); UpdateRasterizerStateInOpenGLContext(ContextState); UpdateDepthStencilStateInOpenGLContext(ContextState); BindPendingShaderState(ContextState); SetupTexturesForDraw(ContextState); CommitNonComputeShaderConstants(); CachedBindElementArrayBuffer(ContextState,0); // Zero-stride buffer emulation won't work here, need to use VAB with proper zero strides SetupVertexArrays(ContextState, 0, PendingState.Streams, NUM_OPENGL_VERTEX_STREAMS, 1); GLenum DrawMode = GL_TRIANGLES; GLsizei NumElements = 0; GLint PatchSize = 0; FindPrimitiveType(PrimitiveType, ContextState.bUsingTessellation, 0, DrawMode, NumElements, PatchSize); if (FOpenGL::SupportsTessellation() && DrawMode == GL_PATCHES ) { FOpenGL::PatchParameteri(GL_PATCH_VERTICES, PatchSize); } FOpenGLVertexBuffer* ArgumentBuffer = ResourceCast(ArgumentBufferRHI); glBindBuffer( GL_DRAW_INDIRECT_BUFFER, ArgumentBuffer->Resource); { CONDITIONAL_SCOPE_CYCLE_COUNTER(STAT_OpenGLShaderFirstDrawTime, PendingState.BoundShaderState->RequiresDriverInstantiation()); FOpenGL::DrawArraysIndirect( DrawMode, INDEX_TO_VOID(ArgumentOffset)); } glBindBuffer( GL_DRAW_INDIRECT_BUFFER, 0); FShaderCache::LogDraw(0); } else { UE_LOG(LogRHI, Fatal,TEXT("OpenGL RHI does not yet support indirect draw calls.")); } }