【UE4 C++】渲染流水线笔记:从MeshBatch到DrawCommand
https://zhuanlan.zhihu.com/p/564066505
在看完PMC的源码后,对UE4的渲染流水线有了一点浅薄的认知。能够明白其中的FPrimitiveSceneProxy通过GetDynamicMeshElements提交FMeshBatch的过程。接下来就需要继续理清MeshBatch到DrawCommand的过程。
为了继续了解InitViews和MeshPassProcessor,需要先看一下引擎的调度逻辑。
Engine\Source\Runtime\Launch\Private\Windows\LaunchWindows.cpp
Windows平台的的入口函数是WinMain,各阶段操作都是调用FEngineLoop的对应方法:
- WinMain
- GuardedMain
- EnginePreInit
- EngineInit
- FEngineLoop::Init
- UGameEngine::Init
- UGameEngine::Start
- FEngineLoop::Init
- EngineTick
- FEngineLoop::Tick
- UGameEngine::Tick
- FEngineLoop::Tick
- EngineExit
- GuardedMain
主要聚焦在Tick如何进入渲染流水线的调用:
- UGameEngine::Tick
- UGameEngine::RedrawViewports
- FViewport::Draw
- UGameViewportClient::Draw
- FRendererModule::BeginRenderingViewFamily
- FSceneRenderer::CreateSceneRenderer - FDeferredShadingSceneRenderer
- RenderViewFamily_RenderThread
- FDeferredShadingSceneRenderer::Render
- FDeferredShadingSceneRenderer::InitViews
- FSceneRenderer::ComputeViewVisibility
- FSceneRenderer::SetupMeshPass
- FParallelMeshDrawCommandPass::DispatchPassSetup
- GenerateDynamicMeshDrawCommands
- FMeshPassProcessor::AddMeshBatch
- FRendererModule::BeginRenderingViewFamily
找到了FMeshDrawCommand生成的主要流程,接下来先看这部分是如何和FPrimitiveSceneProxy配合的。与FPrimitiveSceneProxy先关的最核心的方法是以下三个:
- CreateSceneProxy
- GetViewRelevance
- GetDynamicMeshElements
方法1在FScene::AddPrimitive被调用,使用MarkRenderStateDirty可以进行重建,会将UPrimitiveComponent在游戏线程中的数据拷贝到渲染线程,两者独立操作保证线程安全。
Engine\Source\Runtime\Renderer\Private\RendererScene.cpp
- MarkRenderStateDirty
- DoDeferredRenderUpdates_Concurrent
- RecreateRenderState_Concurrent
- CreateRenderState_Concurrent
- AddPrimitive
- CreateRenderState_Concurrent
- RecreateRenderState_Concurrent
- DoDeferredRenderUpdates_Concurrent
方法2和3都发生在FSceneRenderer::ComputeViewVisibility视椎剔除和遮挡剔除之后。方法2用于确定各个Primitive的配置,确定是否渲染及渲染类型归类,调用链:
- ComputeAndMarkRelevanceForViewParallel
- AnyThreadTask
- ComputeRelevance
- GetViewRelevance
- ComputeRelevance
- AnyThreadTask
方法3开始收集MeshBatch,对于已经归类记录的DynamicMeshElement,通过Collector收集需要提交的MeshBatch,并在其后计算每个MeshBatch会被哪些MeshPass引用,加到对应View相应MeshPass的计数中,调用链:
- GatherDynamicMeshElements
- GetDynamicMeshElements
- ComputeDynamicMeshRelevance
- FMeshPassMask
- NumVisibleDynamicMeshElements
MeshBatch收集完成后,接下来就是转换到DrawCommand的过程,这部分在SetupMeshPass中做了大量的工作。
- SetupMeshPass
- 根据ShadingPath和MeshPass从FPassProcessorManager的JumpTable中获取创建对应PassProcessor的方法。全局注册的方法封装在FRegisterPassProcessorCreateFunction的构造方法中,会将信息填充到JumpTable中。
- 获取对应MeshPass的FParallelMeshDrawCommandPass,调用DispatchPassSetup,将得到的信息传入,开始生成DrawCommand。
void FParallelMeshDrawCommandPass::DispatchPassSetup(
FScene* Scene,
const FViewInfo& View,
EMeshPass::Type PassType,
FExclusiveDepthStencil::Type BasePassDepthStencilAccess,
FMeshPassProcessor* MeshPassProcessor,
const TArray<FMeshBatchAndRelevance, SceneRenderingAllocator>& DynamicMeshElements,
const TArray<FMeshPassMask, SceneRenderingAllocator>* DynamicMeshElementsPassRelevance,
int32 NumDynamicMeshElements,
TArray<const FStaticMeshBatch*, SceneRenderingAllocator>& InOutDynamicMeshCommandBuildRequests,
int32 NumDynamicMeshCommandBuildRequestElements,
FMeshCommandOneFrameArray& InOutMeshDrawCommands,
FMeshPassProcessor* MobileBasePassCSMMeshPassProcessor,
FMeshCommandOneFrameArray* InOutMobileBasePassCSMMeshDrawCommands
)
- DispatchPassSetup
- 这一步开始是逐Pass进行,首先收集生成DrawCommand需要的全部信息,存储在FMeshDrawCommandPassSetupTaskContext中,用于后续并行执行
- 创建TaskGraph,类型为FMeshDrawCommandPassSetupTask,传入收集的Context
- TGraphTask::CreateTask
- ConstructAndDispatchWhenReady
- DoTask
3. 调用GenerateDynamicMeshDrawCommands,将MeshBatch依次加入MeshProcessor,以BasePassMeshProcessor为例:
void FBasePassMeshProcessor::AddMeshBatch(
const FMeshBatch& RESTRICT MeshBatch,
uint64 BatchElementMask,
const FPrimitiveSceneProxy* RESTRICT PrimitiveSceneProxy,
int32 StaticMeshId);
- AddMeshBatch
- 获取当前MeshBatch的FMaterial后调用TryAddMeshBatch
- TryAddMeshBatch,MeshBatch过滤
- Process,Shader获取,渲染状态处理
- BuildMeshDrawCommands,Shader资源绑定
- FinalizeCommand,生成VisibleMeshDrawCommand,加入DrawList
到这里完成了MeshBatch到DrawCommand的过程,有了初步的认知后,源码里相关的概念已经大概熟悉,接下来就可以尝试针对性的修改源码,增加一个独立的渲染Pass了。