[UE4] レンダリングパイプライン備忘録

#1.概要
この記事はUEのレンダリングの内部挙動を知りたいプログラマー向けの記事です。

本当はもっと詳しく調べたかったのですが力尽きました。

#2.環境
* OS : Windows 64bit
* UE : 4.26

#3.描画におけるスレッドの流れ
この章では、UEのレンダリングとスレッドの関係について解説します。

UEのレンダリングはスレッド化されており、レンダリングに関係するスレッドは**GameThread**、**RenderingThread**、**RHIThread**です。UEのスレッド一覧は、名前空間ENamedThreadsでType列挙体として定義されています。

**・UEのスレッドの関係図**
![Thread.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1501035/f7c4f9f3-f181-5b01-e1df-14d53d880755.png)

* **FRunnable** 
スレッド内の処理を定義するためのクラスです。スレッド関数であるRun()を持ち、Run()が実行された後、スレッドのライフサイクルも終了します。FRenderingThread、FRHIThreadがFRunnableを継承します。

* **FRunnableThread**
FRunnableThreadはすべてのスレッドの基本クラスであり、FRunnableをメンバー変数として持ち、Run()を実行します。
FRunnableThreadはプラットフォームに応じて作成されます。

* **FThreadManager**
FThreadManagerはFRunnableThreadを管理します。

##3.1 GameThread
**EQUEUE_RENDER_COMMANDマクロ**を使用して、タスクをRenderingThreadのTaskMapに追加します。

ENQUEUE_RENDER_COMMANDマクロは内部でEnqueueUniqueRenderCommand()を実行します。
RenderingThread内で呼び出された場合、直接FRHICommandListに対してコマンドを積み、そうでない場合TaskMapにタスクを積みます。

通常はメインスレッドがGameThreadとなります。

##3.2 RenderingThread
積まれたタスクを実行し、**FRHICommandList**にコマンドを積みます。
内部では**ALLOC_COMMANDマクロ**を使用して、FRHICommandBaseが積まれています。

RenderThreadはStartRenderingThread()で作成されます。
FRunnableThread::Create()で、FRunnableを継承したFRenderingThreadが指定されます。
また、RenderThreadの状態を監視するためにFRenderingThreadTickHeartbeatも作成されます。

スレッド作成後は、FRenderingThread::Run()内でRenderingThreadMain()が呼び出されます。


##3.3 RHIThread
FRHICommandListに積まれたコマンドを実行します。

RHIThreadはコマンドの実行中ブロックされますが、RenderingThreadはFRHICommandListへの入力命令を引き続き処理できるため、RHIThreadの処理を待つ必要がなく、CPU時間の使用率が向上します。

FRHIThreadはStartRenderingThread()内で、RenderingThreadの前に作成されます。
DX12ではデフォルトで作成されます。
DX11ではデフォルトで作成しないようになっているため、`EXPERIMENTAL_D3D11_RHITHREAD 1`で有効にすることで作成することができるらしいです(未確認)。

###3.3.1 FRHICommandList
FRHICommandListは各描画APIのコマンドをラップします。
コマンドによって、実行されるスレッドがRenderingThreadかRHIThreadに分かれます。

RenderThreadで実行される場合はFDynamicRHIを介して実行され、RHIThreadで実行される場合はIRHICommandContextを介して実行されます。

FRHICommandListはRHIモジュールで定義されています。
RHIモジュールはRHIInit()とPostRHIInit()で初期化されます。

1. RHIInit()内でPlatformCreateDynamicRHI()を呼び出すことで、DX11、DX12、Vulkanなどの各APIのRHIモジュールを作成します。
2. IDynamicRHIModule::CreateRHI()で各APIのFDynamicRHIを作成します。

**・RHIの関係図**
![RHIModule.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1501035/b95d01b9-85f6-3193-f175-085c545afd9e.png)

#4. レベルへのUPrimitiveComponentの追加
この章ではレベルにUPrimitiveComponentを追加した時の処理について解説します。

```C++:呼び出し階層
UActorComponent::ExecuteRegisterEvents()
 UPrimitiveComponent::CreateRenderState_Concurrent()
  FScene::AddPrimitive()
   FScene::AddPrimitiveSceneInfo_RenderThread()
```

FScene::AddPrimive()によってUPrimitiveComponentがFSceneに追加されます。

UPrimitiveComponentのRenderingThread用のデータである、FPrimitiveSceneProxyとFPrimitiveSceneInfoが作成されます。
作成されたら、ENQUEUE_RENDER_COMMANDによってFScene::AddPrimitiveSceneInfo_RenderThread()がRenderingThreadへのタスクとして追加されます。

#5. シーンの描画
この章ではシーンの描画について解説します。

シーンの描画はFSceneRenderer::Render()によって行われます。
GameThreadによってFSceneRenderer::CreateSceneRenderer()が呼び出され、RenderingThread側で使用するSceneRendererのインスタンスが毎フレーム作成及び破棄されます。

PCの場合は遅延描画を行う**FDeferredShadingSceneRenderer**が作成されます。

#5.1. FMeshDrawCommandの生成
描画データは最終的にFMeshDrawCommandに変換されます。

```:FPrimitiveSceneProxyからFMeshDrawCommandへの変換処理
FDeferredShadingSceneRenderer::Render()
  FDeferredShadingSceneRenderer::InitViews()
    FSceneRenderer::ComputeViewVisibility() ビューに関連する可視性の計算
      FPrimitiveSceneInfo::UpdateStaticMeshes()
        CacheMeshDrawCommands() 静的なFMeshDrawCommandのキャッシュ
      ComputeAndMarkRelevanceForViewParallel()
      FSceneRenderer::GatherDynamicMeshElements()
        FPrimitiveSceneProxy::GetDynamicMeshElements() ビューの動的に表示される要素を収集する
      FSceneRenderer::SetupMeshPass()
        FParallelMeshDrawCommandPass::DispatchPassSetup()
          GenerateDynamicMeshDrawCommands() 動的と静的で各パスのFMeshPassProcessor::AddMeshBatchを呼び出す
            FMeshPassProcessor::AddMeshBatch()
   
```

FMeshDrawCommandは、各パスの描画に必要なすべてのリソースを記述します。

静的なデータはCachedMeshDrawCommandStateBucketsにキャッシュされたFMeshBatchを使用し、動的なデータはフレームごとに作成されます。
FMeshProcessorがFMeshBatchをFMeshDrawCommandに変換します。

FMeshBatchはすべてのパスを対して実行するために必要な情報が含まれていますが、一部のパスからすると無駄な情報が含まれています。各パスに最適なデータにするため位FMeshBatchをFMeshDrawCommandに変換します。

#5. 参考資料
[メッシュ描画パイプライン | Unreal Engine ドキュメント]
(https://docs.unrealengine.com/4.26/ja/ProgrammingAndScripting/Rendering/MeshDrawingPipeline/)
[Unreal Engine 4.22 のメッシュ描画パイプライン変換ガイド | Unreal Engine ドキュメント]
(https://docs.unrealengine.com/4.26/ja/ProgrammingAndScripting/Rendering/MeshDrawingPipeline/4_22_ConversionGuide/)
[グラフィック プログラミングの概要 - Unreal Engine ドキュメント]
(https://docs.unrealengine.com/4.26/ja/ProgrammingAndScripting/Rendering/Overview/)
[スレッド化したレンダリング - Unreal Engine ドキュメント]
(https://docs.unrealengine.com/4.26/ja/ProgrammingAndScripting/Rendering/ThreadedRendering/)
[UE4 rendering data transmission - ProgrammerSought]
(https://www.programmersought.com/article/10903637661/)
[UE4's rendering process - ProgrammerSought]
(https://www.programmersought.com/article/20327042442/)
[剖析虚幻渲染体系（04）- 延迟渲染管线- 0向往0 - 博客园]
(https://www.cnblogs.com/timlly/p/14732412.html)
[虚幻4渲染编程(Shader篇)【第十二卷：MeshDrawPipline】]
(https://zhuanlan.zhihu.com/p/61464613)
[虚幻4渲染编程(Shader篇)【第十五卷：MeshDrawCommand的派发机制】]
(https://zhuanlan.zhihu.com/p/94463182)
[调试&截帧 过一遍虚幻渲染管线(上) CPU流程]
(https://zhuanlan.zhihu.com/p/336693523)