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()
      • 結果のデバッグ出力
        • 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

ソースコード (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();
   ...
}

--------------------------------------------------------------------------------

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.