在上篇简单说明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. Math.pow

    一个Math函数,例如:Math.pow(4,3);返回4的三次幂,用法:Math.pow(x,y) x 必需传.底数.必须是数字. y 必需传.幂数.必须是数字. 如果结果是虚数或负数,则该方法将返 ...

  2. HDU 6070 - Dirt Ratio | 2017 Multi-University Training Contest 4

    比赛时会错题意+不知道怎么线段树维护分数- - 思路来自题解 /* HDU 6070 - Dirt Ratio [ 二分,线段树 ] | 2017 Multi-University Training ...

  3. 【SQL-历史执行语句查询】 查询对数据库执行了哪些SQL

    Sql语句 QS.creation_time as '创建时间', ), (( THEN DATALENGTH(st.text) ) ) AS '查询语句' , ST.text as '执行文本', ...

  4. java.util.Queue

    转载于:https://www.runoob.com/java/data-queue.html 队列是一种特殊的线性表,它只允许在表的前端进行删除操作,而在表的后端进行插入操作. LinkedList ...

  5. 牛客寒假算法基础集训营2 【处女座与复读机】DP最小编辑距离【模板题】

    链接:https://ac.nowcoder.com/acm/contest/327/G来源:牛客网 一天,处女座在牛客算法群里发了一句“我好强啊”,引起无数的复读,可是处女座发现复读之后变成了“处女 ...

  6. 【luoguUVA1316】 Supermarket--普通并查集+贪心

    题目描述 有一个商店有许多批货,每一批货又有N(0<=N<=10^4 )个商品,同时每一样商品都有收益P_iPi​ ,和过期时间D_iDi​ (1<=Pi,,Di <=10^4 ...

  7. Java的23种设计模式<一>

    设计模式(Design pattern)是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结.使用设计模式是为了可重用代码.让代码更容易被他人理解.保证代 码可靠性. 毫无疑问,设计模式 ...

  8. Django基础之Session操作

    1. 创建一个示例 1.1 第一步 首先创建一个django项目,创建app01, 连接数据库,做好准备工作. 然后在templates目录下创建两个html: login.html负责登录页面: b ...

  9. 如何让spark sql写mysql的时候支持update操作

    如何让sparkSQL在对接mysql的时候,除了支持:Append.Overwrite.ErrorIfExists.Ignore:还要在支持update操作 1.首先了解背景 spark提供了一个枚 ...

  10. Java基础__Java中集合类

    ArrayList:有序.可重复.线程不安全.内部使用数组进行存储 LinkedList:有序.可重复.线程不安全.内部使用引用进行存储[可以很方便的进行插入.删除数据] Vector:有序.可重复. ...