在上篇简单说明RHI的作用后, 我们在引擎中探索一下RHI的种种细节与实现. 在解决方案资源管理器中搜索RHI, 会有这些文件:

  (1)对应不同运行平台的PlatformDynamicRHI.cpp(上篇有提到, 主要进行硬件接口创建前的配置并创建不同绘图接口对应的RHI). 由于目标平台在打包时就确定了, 在打包时会选择编译这些PlatformDynamicRHI.cpp中的一个, 而其他平台的.cpp会被绕过编译.

                    

  (2)对应不同绘图接口的DynamicRHI. 可以看到有DX11, DX12, Vulkan, Metal的RHI模块.

      

  一顿找却没有找到OpenGL的DynamicRHI, 在管理器搜索发现被集成进了OpenGLDrv.h中:

  (3)RHI的具体功能实现部分:
                    

  重点探索第三部分.

  1.首先看RHI.h/RHI.cpp:

  在这部分, 引擎会定义各种RHI的公共变量, 比如当前显卡硬件的代号以及驱动版本:

/**
* only set if RHI has the information (after init of the RHI and only if RHI has that information, never changes after that)
* e.g. "NVIDIA GeForce GTX 670"
*/
extern RHI_API FString GRHIAdapterName;
extern RHI_API FString GRHIAdapterInternalDriverVersion;
extern RHI_API FString GRHIAdapterUserDriverVersion;
extern RHI_API FString GRHIAdapterDriverDate;
extern RHI_API uint32 GRHIDeviceId;
extern RHI_API uint32 GRHIDeviceRevision; // 0 means not defined yet, use functions like IsRHIDeviceAMD() to access
extern RHI_API uint32 GRHIVendorId; // to trigger GPU specific optimizations and fallbacks
RHI_API bool IsRHIDeviceAMD(); // to trigger GPU specific optimizations and fallbacks
RHI_API bool IsRHIDeviceIntel(); // to trigger GPU specific optimizations and fallbacks
RHI_API bool IsRHIDeviceNVIDIA();

  以及各项着色/绘图特性, 例如在深度测试时获取深度, 体纹理, 硬件合并渲染, 对MSAA的支持, 对各种RenderTarget格式的支持程度, 乃至于光栅化等. 这些特性因硬件平台、绘图接口以及显示驱动的不同会有种种差异.

  2.DynamicRHI

  在这部分会有FDynamicRHI接口的定义以及图形渲染所需要的各种基础接口如创建Buffer, 创建Texture以及创建着色器等:

/** The interface which is implemented by the dynamically bound RHI. */
class RHI_API FDynamicRHI
{
public: ....
}

  例如创建PixelShader和VertexShader这一类操作:

// FlushType: Wait RHI Thread
virtual FPixelShaderRHIRef RHICreatePixelShader(FRHIShaderLibraryParamRef Library, FSHAHash Hash)
{
return nullptr;
} // FlushType: Wait RHI Thread
virtual FVertexShaderRHIRef RHICreateVertexShader(const TArray<uint8>& Code) = ; // FlushType: Wait RHI Thread
virtual FVertexShaderRHIRef RHICreateVertexShader(FRHIShaderLibraryParamRef Library, FSHAHash Hash)
{
return nullptr;
}

  3.FRenderResource

  这也是我们应用中实际操作RHI时较多使用的部分, 定义了许多创建渲染资源时需要的类, 以及众多选择进行实现的函数. 首先看资源类基类FRenderResource:

/**
* A rendering resource which is owned by the rendering thread.
*/
class RENDERCORE_API FRenderResource
{
public:
/** @return The global initialized resource list. */
static TLinkedList<FRenderResource*>*& GetResourceList();
...
/**
* Initializes the RHI resources used by this resource.
* Called when entering the state where both the resource and the RHI have been initialized.
* This is only called by the rendering thread.
*/
virtual void InitRHI() {} /**
* Releases the RHI resources used by this resource.
* Called when leaving the state where both the resource and the RHI have been initialized.
* This is only called by the rendering thread.
*/
virtual void ReleaseRHI() {}
...
}

  会有众多纯虚函数暴露出来供我们选择重写. 具体接口功能在注释中有很完备的写出. 如果要具体地操作RHI, 这个资源类也会是我们进行了解与学习的入口, 这些接口是首先要了解的内容. 

  例如创建一个简单的IndexBuffer, 我们会在RenderResource中发现IndexBuffer的基类:

/** An index buffer resource. */
class FIndexBuffer : public FRenderResource
{
public:
FIndexBufferRHIRef IndexBufferRHI; /** Destructor. */
virtual ~FIndexBuffer() {} // FRenderResource interface.
virtual void ReleaseRHI() override
{
IndexBufferRHI.SafeRelease();
}
virtual FString GetFriendlyName() const override { return TEXT("FIndexBuffer"); }
};

  会了解到对于这个IndexBuffer对应的Resource, 引擎为我们实现了Realease, 而Init的工作就需要我们亲力完成. 即可如此创建子类:

/* Index Buffer */
class FMyMeshIndexBuffer : public FIndexBuffer
{
public:
int32 NumIndices;
virtual void InitRHI() override
{
FRHIResourceCreateInfo CreateInfo;
IndexBufferRHI = RHICreateIndexBuffer(sizeof(int32), NumIndices * sizeof(int32), BUF_Dynamic, CreateInfo);
}
};

  此外会有常用接口如:

//开始创建Resource
void BeginInitResource(FRenderResource* Resource)
{
ENQUEUE_RENDER_COMMAND(InitCommand)(
[Resource](FRHICommandListImmediate& RHICmdList)
{
Resource->InitResource();
});
}
//开始更新ResouceRHI
void BeginUpdateResourceRHI(FRenderResource* Resource)
{
ENQUEUE_RENDER_COMMAND(UpdateCommand)(
[Resource](FRHICommandListImmediate& RHICmdList)
{
Resource->UpdateRHI();
});
}
...

  4. RHICommandList

  在看源码之前先要了解一下RHICommandList的机制: RHI的指令会压入RHICommandList中, 而这个List本身是存放在渲染线程中的. RHI的工作是管理各个RHI对象, 在不干扰渲染线程工作的情况下把这些对象的Command压入到渲染线程的CommanList中, 以及为渲染线程做各种复杂的异步操作而不干涉到渲染线程的正常工作.(皇上我把文件都给你整理好了你批就行了...)

  FRHICommandBase & FRHICommand: 初看到FRHICommandBase时会略感懵逼...

struct FRHICommandBase
{
FRHICommandBase* Next = nullptr;
virtual void ExecuteAndDestruct(FRHICommandListBase& CmdList, FRHICommandListDebugContext& DebugContext) = ;
};

  其实看类名和虚函数名可以看出这样一个结构体是一条RHI指令的一个基类, 其中的CommanBase空指针就是为了构成CommanList链表而生的. 每个Command下的函数指针会携带一条指令, 而Next指针又会指向下一条Command, 渲染线程只需不停地读这个链表执行这些指令进行工作就可以了.

  而在其之下会有一个类模板子类FRHICommand:

template<typename TCmd>
struct FRHICommand : public FRHICommandBase
{
void ExecuteAndDestruct(FRHICommandListBase& CmdList, FRHICommandListDebugContext& Context) override final
{
TCmd *ThisCmd = static_cast<TCmd*>(this);
#if RHI_COMMAND_LIST_DEBUG_TRACES
ThisCmd->StoreDebugInfo(Context);
#endif
ThisCmd->Execute(CmdList);
ThisCmd->~TCmd();
} virtual void StoreDebugInfo(FRHICommandListDebugContext& Context) {};
};

  需要模板TCmd实现方法Execute. 而此子类在执行ExecuteAndDestruct时会退化到参数类型TCmd, 执行TCmd下的Execute方法后自动析构, 这个过程也就实现了所谓"ExecuteAndDestruct".

  

UE4 RHI(2)的更多相关文章

  1. UE4 RHI与Render模块简解

    UE4中的RHI指的是Render hardware interface,作用像Ogre里的RenderSystem,针对Dx11,Dx12,Opengl等等平台抽象出相同的接口,我们能方便能使用相同 ...

  2. UE4 RHI与条件式编译

    RHI即RenderHardwareInterface, 即渲染硬件接口, 是UE为实现跨平台而实现的一套API. 每个RHI接口都为OpenGL, Vulkan, DX11等做了不同的实现. 在引擎 ...

  3. UE4入门与精通

    由于目前在使用UE4引擎,多少也有一些心得,比如在日常使用中会遇到一些问题.坑(潜规则)或者一些使用技巧等.本人决定开一个大坑,主要有两个目的:一是可以自己做个记录,二是可以给大家提供一些参考吧.主要 ...

  4. UE4 在C++ 动态生成几何、BSP体、Brush ---- Mesh_Generation

    截至UE4  4.10 runtime 无法生成BSP类 ,只能通过自定义的Mesh的Vertex 进行绘制 ( Google 考证,能改UE4源码的请忽略 ) 可用到的 UE4 集成的Render ...

  5. 移植UE4的模型操作到Unity中

    最近在Unity上要写一个东东,功能差不多就是在Unity编辑器上的旋转,移动这些,在手机上也能比较容易操作最好,原来用Axiom3D写过一个类似的,有许多位置并不好用,刚好在研究UE4的源码,在模型 ...

  6. UE4/Unity3D中同时捕获多高清摄像头的高效插件

    本文主要讲实现过程的一些坑. 先说下要实现的目标,主要功能在UE4/Unity中都要用,能同时捕获多个摄像头,并且捕获的图片要达到1080p25桢上,并且需要经过复杂的图片处理后丢给UE4/Unity ...

  7. UE4命令行使用,解释

    命令行在外部 从命令行运行编辑项目 1 导航到您的[LauncherInstall][VersionNumber]\Engine\Binaries\Win64 目录中. 2 右键单击上 UE4Edit ...

  8. 初探UE4中的Profiling【转】

    http://blog.ch-wind.com/ue4-profiling-preview/ Profililng是成品制作过程中非常重要的一个步骤,通过Profiling才能提高运行效率使得作品达到 ...

  9. 探究光线追踪技术及UE4的实现

    目录 一.光线追踪概述 1.1 光线追踪是什么 1.2 光线追踪的特点 1.3 光线追踪的历史 1.4 光线追踪的应用 二.光线追踪的原理 2.1 光线追踪的物理原理 2.2 光线追踪算法 2.3 R ...

随机推荐

  1. 粗暴,干就完了----徐晓冬似的C语言自学笔记---字符数组相关技术

    字符串拼接函数 strcat() 字符串----作为很多编程语言的鼻祖C语言,没有字符串类型,取而代之的字符数组,很多数组在声明的时候会给定数组长度,然而我们却可以这样写 char mywords[] ...

  2. 适配器模式(Adapter)---结构型

    1 基础知识 定义:将一个类的接口(被适配者)转换成客户期望的另一个接口(目标).特征:使原本接口不兼容的类可以一起工作. 本质:转换匹配,复用功能.把不兼容的接口转换为客户端期望的样子从而实现功能的 ...

  3. demo(一) react-native-router-flux

    react-native init AwesomeProject cd AwesomeProject 安装模块 npm i react-native-router-flux --save

  4. @ComponentScan什么时候可以不加

    SpringBoot在没配置@ComponentScan的情况下,默认只扫描和主类处于同包下的Class. 主类Application.java: import org.springframework ...

  5. MySQL认识索引

    什么是索引? 索引在MySQL中也叫是一种“键”,是存储引擎用于快速找到记录的一种数据结构.索引对于良好的性能非常关键,尤其是当表中的数据量越来越大时,索引对于性能的影响愈发重要.索引优化应该是对查询 ...

  6. 总结调试webview的方式(安卓)

    参考文章: 移动端真机调试指南 Mac 平台 Android 使用 Charles 抓包方法 Charles使用Map Local和Rewrite提高开发效率 通过chrome直接进行调试 chrom ...

  7. AT3912 Antennas on Tree

    AT3912 Antennas on Tree %%zzt 只能考虑性质了. 把最后选择的k个点的连通块求出来,连通块内部的点表示都是互异的 连通块外部的点只能形成若干条链,并且这k个点的每一个最多与 ...

  8. [NOIP2018]:旅行(数据加强版)(基环树+搜索+乱搞)

    题目描述 小$Y$是一个爱好旅行的$OIer$.她来到$X$国,打算将各个城市都玩一遍.小$Y$了解到,$X$国的$n$个城市之间有$m$条双向道路.每条双向道路连接两个城市.不存在两条连接同一对城市 ...

  9. mybatis 语句中where 后边要跟必要条件和多个选择条件处理方法

    <select id="serchRelation" resultType="Relation">SELECTr.node_one as nodeO ...

  10. 在iOS开发中使用icon font的方法

    http://iconfont.cn/help/iconuse.html 在开发阿里数据iOS版客户端的时候,由于项目进度很紧,项目里的所有图标都是用最平常的背景图片方案来实现.而为了要兼容普通屏与R ...