UE4 Parallel Rendering Overview の日本語メモ
- UE4 : Parallel Rendering Overview
- 上の英語のドキュメントについての自分用の日本語メモです.
一番最初
- ゲームスレッドとレンダースレッドだけ
- ゲームスレッドがレンダースレッド向けに, コマンドをエンキューしていた.
- レンダースレッドはこのコマンドを元に RHI レイヤーを叩いて, プラットフォームごとの API ( Lock, DrawPrimitive など)を実行していた
- しかし, (描画のためのコマンド生成を) 並列化しにくい問題があった
並列化: ステージ 1
- レンダラーの処理をフロントエンドとバックエンドに分ける
- レンダースレッド ( コマンド生成 )
- RHI レイヤーを叩かない
- 代わりに FRHICommandList クラスを継承したクロスプラットフォーム対応のコマンドを作成する
- もし DrawPrimitive をしたいときには, RHICommandList に DrawPrimitive コマンドをエンキューする
- RHI スレッド( コマンド実行 )
- レンダースレッドとは別スレッド
- RHICommandList を元に, プラットフォームごとの API ( RHI レイヤー)を実行する
並列化: ステージ2
- タスク並列ではなくデータ並列
- それぞれの描画パスをチャンクに分割する
- レンダースレッドは描画パスのコマンドを並列に作るためのワーカースレッドをキックして, それらのワーカースレッドのタスクの完了を待たない
- 並列化の対象の描画パスはベースパス, デプスパス, ベロシティパスなど
- FParallelComamndListSet クラスがあり, これを継承したクラス( FBasePassParallelCommandListSet など)を作る
- これらのクラスは各スレッドで RHICommandList を作って, 適切な順序でサブミットし 部分的なコマンドリストを作る
- ワーカースレッドの負荷のバランスは UE4 で調整してくれる
並列化: ステージ3
- バックエンドの並列化に対応しているコンソールや DX12, Vulkan 向けのもの
- 基本的にはフロントエンドで並列生成されたものを, バックエンドで並列変換する
- プラットフォーム API ごとに, IRHICommandContext クラスを継承したものが作られて利用される
同期
RHI スレッドなしの場合
- ゲームスレッド
- N+1 フレームを処理している
- レンダースレッド
- Nフレームか
- N+1 フレーム(ゲームスレッドからのエンキューが速い場合)
- を処理している
RHI スレッドありの場合
- レンダースレッド
- 「RHI スレッドがフレーム N を完了する前」に「フレーム N+1の可視性の計算」を終わらせることが許可されている
- なので…
- ゲームスレッド
- N+1 フレームの計算
- レンダースレッド
- N+1 フレーム または
- N フレームの計算
- RHI スレッド
- N+1 フレーム または
- N フレームの変換処理
- これらの同期は FrameEndSync や FRHICommandListImmediate::RHIThreadFence で行っている
- 関連 URL
- 関連するコンソール変数
- rhi.SyncInterval 2
- r.GTSyncType 2
- r.OneFrameThreadLag 1
- r.Vsync 1
- rhi.SyncSlackMS 0
- r.rhicmdusedeferredcontexts
- Will control parallelization of the backend.
- r.rhicmduseparallelalgorithms
- Will control parallelization of the frontend.
- r.rhithread.enable
- Will disable the RHIThread completely. Commandlists will still be generated, they will just be translated directly on the RenderThread at certain points.
- r.rhicmdbypass
- ProfileGPU
- void FGPUProfilerEventNodeFrame::DumpEventTree() でテキスト出力
- イベント PUsh
- SCOPED_DRAW_EVENT(RHICmdList, BeginRenderingPrePass);
- void TDrawEvent<TRHICmdList>::Start(TRHICmdList& InRHICmdList, FColor Color, const TCHAR* Fmt, …)
- void TDrawEvent<TRHICmdList>::Stop()
- SCOPED_DRAW_EVENT(RHICmdList, BeginRenderingPrePass);
- 結果のデバッグ出力
- FGPUProfileStatSummary Summary;
for (int32 BaseNodeIndex = 0; BaseNodeIndex < EventTree.Num(); BaseNodeIndex++)
{
DumpStatsEventNode(EventTree[BaseNodeIndex], RootResult, 0, RootWildcard, false, /*inout*/ Summary);
}
Summary.PrintSummary(); - /** Recursively dumps stats for each node with a depth first traversal. */
static void DumpStatsEventNode(FGPUProfilerEventNode* Node, float RootResult, int32 Depth, const FWildcardString& WildcardFilter, bool bParentMatchedFilter, FGPUProfileStatSummary& Summary)
{ … } - 結果のノード
class FGPUProfilerEventNodeStats : public FRefCountedObject- http://api.unrealengine.com/INT/API/Runtime/RHI/FGPUProfilerEventNodeStats/index.html
- FGPUProfileStatSummary Summary;
- イベント PUsh
- void FGPUProfilerEventNodeFrame::DumpEventTree() でテキスト出力
ソースコード (UE4.19.1 )
FlushRenderingCommands() は内部で FRenderCommandFence Fence; Fence.BeginFence(); Fence.Wait(); Fence.BeginFace() は内部で FRHISyncFrameCommand Command(CompletionEvent, GTSyncType); Command.Execute(RHICmdList); FRHISyncFrameCommand::Execute() はGTSyncType == 1 のときに RHI スレッドを同期 ------------------------------------------------------ LaunchEngineLoop.cpp void FEngineLoop::Tick() { ... // ゲームスレッド内でのレンダースレッドと同期する場合 { static FFrameEndSync FrameEndSync; static auto CVarAllowOneFrameThreadLag = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.OneFrameThreadLag")); FrameEndSync.Sync( CVarAllowOneFrameThreadLag->GetValueOnGameThread() != 0 ); } } // ゲームスレッドに対して, レンダースレッドを 1 フレーム遅らせるためのクラス class FFrameEndSync { FRenderCommandFence Fence[2]; /** * Syncs the game thread with the render thread. Depending on passed in bool this will be a total * sync or a one frame lag. */ ENGINE_API void Sync( bool bAllowOneFrameThreadLag ) { ... Fence[EventIndex].Wait(bEmptyGameThreadTasks); ... } }; --------------------------------------------------------------------------------- void FRenderCommandFence::Wait(bool bProcessGameThreadTasks) const や void AdvanceFrameRenderPrerequisite() の中で下のゲームスレッドの待ちをしている. /** * Block the game thread waiting for a task to finish on the rendering thread. */ static void GameThreadWaitForTask(const FGraphEventRef& Task, bool bEmptyGameThreadTasks = false) void FViewport::EnqueueBeginRenderFrame() { AdvanceFrameRenderPrerequisite(); ... } --------------------------------------------------------------------------------