拙訳
このトークが扱わないこと
- Destinyのグラフィクスアルゴリズム
- Destinyのシェーダテクニック
- これらについてもっと学びたいなら、
- 我々のテクニックのいくつかはSIGGRAPH 2009〜2014のトークやI3D 2015のキーノートを参照
前提
- タスクベースの並列化、ジョブマネージャの設計、同期プリミティブの基本に精通していること
- 役に立つDestinyエンジン設計は以下でその詳細を述べる
- Multithreading the Entire Destiny Engine by Barry Genova (3月5日火曜日 午後4時〜午後5時)
本日のオススメ
- Destinyのコアレンダラアーキテクチャ
- GPUデータフローへのゲームシミュレーション
- ジョブ化と負荷分散の検討事項
- レイテンシリダクションテクニック
- 入力およびレンダリングレイテンシリダクション
- GPUを完全に飽和させ続ける
- 複雑さのカプセル化
まえがき
- 粒度の粗い並列化
- Destinyのレンダラの目標
- シミュレーションとレンダリングの分離
- コアワークロードのジョブ化
- データ駆動レンダリングサブミッション
- 高度な最適化
- おわりに
粒度の粗い並列化
A 10,000’ View of a Frame in a Game
ゲームの1フレームで起こっていることを高レベルで見ていこう。
A 10,000’ View of a Frame in a Game
- ゲームオブジェクトをシミュレートする
すべてのgame tickごとに、ゲームシミュレーションを走らせることから始める。これは物理エンジン、AI、アニメーション、ゲームプレイに影響を与えるいずれかのコードをを走らせる所である。
A 10,000’ View of a Frame in a Game
- ゲームオブジェクトをシミュレートする
- レンダリングするものを決定する
そして、最新のシミュレーションデータを使って、見えているであろう要素を決定する。
A 10,000’ View of a Frame in a Game
- ゲームオブジェクトをシミュレートする
- レンダリングするものを決定する
- GPUコマンドを生成する
そして、これらの要素をGPUコマンドのセットに変換する。我々はこの操作を”CPUサブミット”と呼んでいる。この結果はGPUに流し込むGPUコマンドバッファである。
A 10,000’ View of a Frame in a Game
- ゲームオブジェクトをシミュレートする
- レンダリングするものを決定する
- GPUコマンドを生成する
- GPUワークを処理する
- 表示する
これらをバックバッファへの出力結果へと処理し、フリップしてプレイヤーへ表示する。
A 10,000’ View of a Frame in a Game
- ゲームオブジェクトをシミュレートする
- レンダリングするものを決定する
- GPUコマンドを生成する
- GPUワークを処理する
- 表示する
このパイプラインはsystem-on-a-thread設計にうまく対応する
System-on-A-Thread設計
Halo: Reachでは、スレッドのいくつかに主要なゲーム関数を対応させた。例えば、レンダリング、オーディオ、シミュレーションには単独のスレッドを対応させた。ここでは、各スレッドはハードウェアスレッドにも対応させた。
実行を見るための異なる方法として、frame tick diagramがある。
ここには、N番目のGame Tickでは、シミュレーションはフレームNのgame tickを計算しつつ、前のフレームをレンダリングしていることが示されている。
シミュレーションが完了したら、次のgametick中にこのフレームをレンダリングし始めるようにgamestateをコピーする。
Halo: Reachでは、すべての出力システムの処理はシミュレーションスレッドが現在のgame tickのワークを終えて、完全なgamestateをコピーした後にのみ始めていた。
完全なgamestateのコピーを用いて各レンダリングループの初めにシリアライズされた可視性の計算をを走らせた。可視性が行われるまで、ドローコールを生成し始めることはできない。
System-on-A-Threadの負荷分散
静的なスレッドごとの負荷分散は準最適なワークロード分布を意味した。
シミュレーションやレンダリングループ(最大のワークロード)ではそのスレッドは高い使用率を示す傾向にあったが、他のスレッドはたくさんのアイドル時間が確認された。
System-on-A-Threadの所見: 悪い点
- 世代やプラットフォームをまたいでの採用は難しい
- ヘテロジニアスなプラットフォームでスケールしない
このアプローチもまた、(PS3のような)ヘテロジニアスな計算システムとか、コアやスレッドの異なるレイアウトを持つシステムとかではうまくスケールしない。
System-on-A-Threadの所見: 悪い点
- 世代やプラットフォームをまたいでの採用は難しい
- ヘテロジニアスなプラットフォームでスケールしない
- 同期必須の完全なgamestateのダブルバッファ
レンダリングに関係ない相当量のデータ(例えば、物理状態やアニメーションのステートマシン)を含むゲームステート全体の完全なコピーに対してコストをかけている。
System-on-A-Threadの所見: 悪い点
- 世代やプラットフォームをまたいでの採用は難しい
- ヘテロジニアスなプラットフォームでスケールしない
- 同期必須の完全なgamestateのダブルバッファ
- シリアライズされた先払いの重い可視性コスト
- GPU idle bubblesの可能性
直列化された可視性の計算はGPU idle bubblesや潜在的により長いレイテンシーを意味し得るかもしれない。
System-on-A-Threadの所見: 良い点
- 便利なデータアクセス
- 拡張可能
- 簡単
しかし、この設計は使いやすく、拡張性があり、コーディングするのに都合が良い。
System-on-A-Threadの所見: 良い点
- 便利なデータアクセス
- 拡張可能
- 簡単
- 単純なスレッディングモデル
また、スレッディングモデルがとても素直なので、複雑なコンカレンシーの問題を起こしたりしない。
System-on-A-Threadの所見: 良い点
- 便利なデータアクセス
- 拡張可能
- 簡単
- 単純なスレッディングモデル
- シミュレーションとレンダリングのパイプライン化された並行実行
我々はシミュレーションとレンダリングをオーバーラップしているので、各フェーズでより多くの処理を行うことができるかもしれない。複雑なAIや物理計算があって、そのシミュレーションに全フレーム時間がかかることがあるかもしれないとき、このパイプライン化は重要である。
まえがき
- 粒度の粗い並列化
- Destinyのレンダラの目標
- シミュレーションとレンダリングの分離
- コアワークロードのジョブ化
- データ駆動レンダリングサブミッション
- 高度な最適化
- おわりに
Destinyレンダラの目標
これらすべてを念頭に置いて、我々のDestinyエンジンで達成しようとした目標とは何だったのか?
Destinyレンダラの目標
- 素晴らしいビジュアルと即応性の高いゲームプレイを伴うゲームを出荷する
- 複雑で、生き生きとした、美しい世界
- 多様な環境を伴う多数の行き先
- 高品質なライティング、動的な時刻変化、リアルタイムシャドウ、天候要素、高解像度レンダリング
- 多くのグラフィクス特徴
Destinyレンダラの目標
- 素晴らしいビジュアルと即応性の高いゲームプレイを伴うゲームを出荷する
- 証明されたレンダラアーキテクチャ
- 4つのコンソールプラットフォームにわたる1.7百万のプレイヤー
Destinyレンダラの目標
- 素晴らしいビジュアルと即応性の高いゲームプレイを伴うゲームを出荷する
- スケーラブルにしておこう
- 世代間やプラットフォーム間
- すべてのプラットフォーム上で堅実なパフォーマンス
クロスプラットフォームとクロスジェネレーション
- 前世代
- PlayStation 3
- 2つのハードウェアPPUスレッド
- 約6つのSPUスレッド
- Xbox 360
- 6つのハードウェアCPUスレッド
- 3つの3.2GHzのIBM PowerPCプロセッサ
- PlayStation 3
- 現世代
- PlayStation 4
- 4つのx64コアを持つ2つのクラスタ
- コアあたり1つのハードウェアスレッド
- Xbox One
- 4つのx64コアを持つ2つのクラスタ
- コアあたり1つのハードウェアスレッド
- PlayStation 4
サンドボックス == 負荷が変化しやすい
サンドボックス == 負荷が変化しやすい
サンドボックス == 負荷が変化しやすい
効率的に保つ
- 最大CPUおよびGPU占有率
- これらを完全に専有し続ける
- GPUアイドルを避ける
- 動的な負荷分散と賢いジョブバッチ処理
- レイテンシを低く保つ
Destinyレンダラの目標
- 素晴らしいビジュアルと即応性の高いゲームプレイを伴うゲームを出荷する
- スケーラブルに保つ
- 効率的に保つ
- 単純に保つ
- “心配するのをやめてマルチスレッディングを愛することを学んだ方法”
- 10分でわかるゼロからジョブをレンダリングするまで
- 新機能 -> 既存ジョブへの変更は最小限に
Destinyレンダラの目標
- 素晴らしいビジュアルと即応性の高いゲームプレイを伴うゲームを出荷する
- スケーラブルに保つ
- 効率的に保つ
- 単純に保つ
- レンダリングからシミュレーションを分離する
Destinyレンダラの目標
- 素晴らしいビジュアルと即応性の高いゲームプレイを伴うゲームを出荷する
- スケーラブルに保つ
- 効率的に保つ
- 単純に保つ
- レンダリングからシミュレーションを分離する
- 完全にデータ駆動なレンダリングパイプライン
実際には…
実際には…
実際には…
実際には…
実際には…
実際には…
まえがき
- 粒度の粗い並列化
- Destinyのレンダラの目標
- シミュレーションとレンダリングの分離
- コアワークロードのジョブ化
- データ駆動レンダリングサブミッション
- 高度な最適化
- おわりに
レンダリングからのゲームステートのトラバーサルの分離
意識的な切り離し
- 可視性とゲームオブジェクトを分離する
- ゲームオブジェクトに依存しない可視性のトラバーサル
- 可視性の結果からレンダリング負荷を操る
- レンダリングとゲームオブジェクトを分離する
静的データをしまう
- 単純な所見:
- レンダリングデータ != ゲームオブジェクトデータ
- レンダリングデータの大半はオブジェクトの寿命で静的である
- レンダラで不変なレンダリングデータをキャッシュする
- 登録時に
- 登録後は読み込みのみ
Carry On Only!
- フレーム毎の一時的なレンダリング固有データを抽出する
- 静的データはすでにキャッシュされている
- 動的データのみを抽出する --- 各フレームごと
Carry On Only!
- フレーム毎の一時的なレンダリング固有データを抽出する
- 静的データはすでにキャッシュされている
- 動的データのみを抽出する --- 各フレームごと
- 可視性の結果は抽出すべきゲームステートを定義する
- 可視要素に対するデータのみを抽出する
オブジェクトとレンダリングの分離
- 各システムで表現を分ける
- オブジェクトシステム(ゲーム側)
- レンダリング(レンダリングオブジェクト)
- 可視性(可視性オブジェクト)
- レイヤ間で通線するためのインターフェイスを提供する
- 厳密なアクセス規則
- 特定のゲームフェイズの間のみ
データフロー
データフロー
データフロー
データフロー
フレームパケット
- 動的なフレーム毎のレンダリングデータのストレージ
- すべての特徴的なレンダラの動的データはここに格納される
- 完全にステートレス、毎フレーム後に投げ捨てられる
- 小さな総フットプリント
- PS3とXbox360でフレームあたり1MB
- ゲームステート全体の9%
- PS3とXbox360でフレームあたり1MB
まえがき
- 粒度の粗い並列化
- Destinyのレンダラの目標
- シミュレーションとレンダリングの分離
- コアワークロードのジョブ化
- データ駆動レンダリングサブミッション
- 高度な最適化
- おわりに
ビューの処理
- ビュー == {錐台|カメラ}
ビューの処理
- ビュー == {錐台|カメラ}
- 例:
- プレイヤービュー シャドウビュー 頭上マップビュー
- 例:
ビューの処理
- ビュー == {錐台|カメラ}
- 例: プレイヤー、シャドウ、頭上ビュー、など
- ビュー == レンダリングジョブチェイン単位
- 可視性..抽出..準備..| サブミット
ビュージョブチェイン
ビュージョブチェイン
- コアシステムジョブ
- 常に実行する
- フレームの内容に依存しない
ビュージョブチェイン
- データ駆動のビュージョブチェイン
- 動的に生成される
- 与えられたフレームでの内容に基づく
ビュージョブチェイン
ビュージョブチェイン
- ビューを決定する
- プレイヤービュー?
- シャドウビュー?
- など
ビュージョブチェイン
- ビューを決定する
- フレームとビューパケットを予約する
- 重い割当やヒープ等なし
抽出
抽出
- ビューごとに可視レンダリングオブジェクトのリストを計算する
- 可視性リングバッファに格納する(一時的)
抽出
- ビューチェインジョブをレンダノード上で処理する
- レンダノードは効率的で、超コンパクトで、キャッシュコヒーレントな表現
- レンダノードのコヒーレントな配列にデータを送る
抽出
- レンダオブジェクトタイプで可視リストをソートする
- のちの実行のコヒーレンシーのため
- ビューパケットにレンダノードを割り当てる
抽出
- レンダビューノードはビュー依存のデータを格納する
- 独自のフレームパケットデータを割り当てられる
- レンダオブジェクトタイプに基づいて
抽出
- フレームノードを持つビューの間でデータを共有する
- フレームパケット内でのデータ共有
- 冗長なデータを回避する
抽出
抽出
抽出
抽出
抽出
抽出
抽出
抽出
抽出
抽出ウィンドウとレイテンシ
- 抽出はシミュレーションが次に進むのを妨げる
- ゲームステートは読み込みに固定されている
- 抽出は最もレイテンシに影響を与える負荷となる
- 視野を広く持つこと?が極めて重要である
- でも、これでは足りない
抽出ウィンドウの最適化
- 抽出ウィンドウは以下から成る:
- すべてのビューに対する可視性の計算
- ゲームステートデータの抽出
- 可視性の計算はCPUの仕事である
- 可視性を抽出の外に移すことは可能か?
- プレイヤービューの静的環境の可視性は最も重い負荷である
予測的な可視性
予測的な可視性
予測的な可視性
抽出 対 準備
- 抽出:
- 最小のしごとで動的データをコピーする
- いづれの複雑なデータ変換をも行わない
- 終了して、次のゲームティックのためにゲームステート/シミュレーションをアンロックする
準備
- 可視要素のみに対して準備ジョブを実行する
- できるならばLODに基づいて計算を省略する
- 知覚できないであろうものは計算しない
準備とシミュレーション
準備
準備
準備
準備
発行
サブミット
フレーム終わり
単純に保つ…!
- グラフィクス機能開発を単純化する
- レンダリング負荷のための透過的なジョブ化
- コア負荷のキャッシュコヒーレンシーと適切な同期を保証する
- 解決策: 機能の抽象化とカプセル化
静的な環境インスタンス
ギアオブジェクト
地形
樹木と装飾物
空オブジェクト
レンダフィーチャ
- レンダフィーチャはグラフィクスレンダリングの単位である
- 以下で定義される:
- キャッシュコヒーレントなデータ表現
- データ管理とレンダリングのためのコードパス
- 以下で定義される:
- フィーチャレンダラインスタンスに対応する
Destinyのフィーチャレンダラ
- グラフィクスフィーチャが以下に対応する方法を定義する
- キャッシュされたデータ表現を持つレンダオブジェクト
- フレームパケットのレンダノード表現
- 各フェーズに対するジョブのエントリポイント
ゲームオブジェクトとフィーチャ
- ゲームオブジェクト1つに対してレンダオブジェクトnつ
- 静的または動的マッピング
ゲームオブジェクトとフィーチャ
- ゲームオブジェクト1つに対してレンダオブジェクトnつ
- 静的または動的マッピング
- 例: Hunter
- 布のフィーチャレンダラ
ゲームオブジェクトとフィーチャ
- ゲームオブジェクト1つに対してレンダオブジェクトnつ
- 静的または動的マッピング
- 例: Hunter
- 布のフィーチャレンダラ
- Skinnedオブジェクトのフィーチャレンダラ
ゲームオブジェクトとフィーチャ
- ゲームオブジェクト1つに対してレンダオブジェクトnつ
- 静的または動的マッピング
- 例: Hunter
- 布のフィーチャレンダラ
- Skinnedオブジェクトのフィーチャレンダラ
- ギアのフィーチャレンダラ
フィーチャレンダラのジョブ化
- 各フィーチャレンダラは各フェーズのエントリポイントのセットを公開している
- 制約されるデータアクセス:
- 対応するノードステートとフィーチャレンダラのオブジェクトデータの読み込みのみ
- フレームパケットデータへの出力のみ
- すべての出力に対してダブルバッファリングが自動で行われる
- 制約されるデータアクセス:
フィーチャレンダラのジョブ化
- コアレンダラはこれらのエントリポイントを用いて各フェーズに対してジョブ化する
- リーフフィーチャに影響を与えずに
- リーフフィーチャは、コアアーキテクチャがジョブ依存性や負荷分散規則を再構築するときに、影響を受けない
フィーチャレンダラのエントリポイント
フレーム抽出開始時 フレーム毎抽出 ビュー毎抽出 ビュー毎抽出ファイナライズ フレーム抽出ファイナライズ時
フレーム準備開始時 フレーム毎準備 ビュー毎準備 ビュー毎準備ファイナライズ フレーム準備ファイナライズ時
サブミットノードブロック開始時 サブミットノード サブミットノードブロック終了時
フィーチャレンダラのエントリポイント
フィーチャレンダラのエントリポイント
フィーチャレンダラのエントリポイント
フィーチャレンダラのエントリポイント
フィーチャレンダラのエントリポイント
フィーチャレンダラのエントリポイント
パフォーマンスとメモリの最適化
- アクセス頻度によってデータと負荷を整理する
- ビュー間、レンダオブジェクト間
- 共有データ: フレームパケットメモリに保存する
- パフォーマンス節約: 頻度ごとに一回だけ高価な計算を行う
パフォーマンスとメモリの最適化
- コアアーキテクチャはアクセスと実行が同期される
- 共有要素へのマルチスレッドアクセス
パフォーマンスとメモリの最適化
- タイトな内部ループでの手当たり次第のロックはパフォーマンスを殺す
パフォーマンスとメモリの最適化
- タイトな内部ループでの手当たり次第のロックはパフォーマンスを殺す
- 高速な共有レンダノード操作のための独自のロックレスプリミティブを開発した
パフォーマンスとメモリの最適化
- フレーム毎の抽出|準備操作
- いずれかのビューに見えているなら、フレームあたり一度
- フレーム毎ノードインデックスにおけるロックレス同期プリミティブ
- レンダラと一緒に登録されたすべてのオブジェクトでユニーク
パフォーマンスとメモリの最適化
- ゲームオブジェクト毎の抽出|準備操作
- 複数レンダオブジェクトに対するワークロード共有
- Skinned抽出および準備、動的AO計算、フォワードライトプロブ計算、非決定的アニメーション解決
- オブジェクトスケルトンハッシュに基づくロックレス同期プリミティブ
フィーチャの例: 静的デカール
- サブミットノードはレンダノードひとつだけで動作する
- 開始/終了ブロックに対するステートブロック最適化
フィーチャの例: Skinned
- すべてのエントリポイントは個別のノード/オブジェクトで操作する
- オブジェクト毎のデータとワークシェアリングに依存する
高度なフィーチャの例: 布
- グローバル(すべての布ブジェクト)操作を行う
- オブジェクト毎データとワークシェアリングに依存する
高度なフィーチャの例: 布
- Havokのクロスシミュレーションジョブを起動および管理する
新しいフィーチャレンダラの追加
- フィーチャタイプ列挙子
my_new_featureを新しく作る s_my_render_entityを作るmy_new_featureに対して外部システム(オブジェクトシステムなど)からregister/unregisterを呼ぶ- my_render_entityを用いるためにexample_feature_renderer.inlを変換する(置換)
- エントリポイントを書き込む(通常で5つ程度、高度なものだと10つ程度)
まえがき
- 粒度の粗い並列化
- Destinyのレンダラの目標
- シミュレーションとレンダリングの分離
- コアワークロードのジョブ化
- データ駆動レンダリングサブミッション
- 高度な最適化
- おわりに
データ駆動サブミッションパイプライン
サブミット
完全なフレーム
大気
Gバッファパス
シャドウ
ディファードライト
加算デカール
透明物
ライトシャフト遮蔽
レンズフレア
最終合成
フレームサブミッション
- 高レベルサブミットコードは複数パスを実行する
- 分岐が多く、ゲームの状態に基づく
フレームサブミッション
- 高レベルサブミットコードは複数パスを実行する
- 不定期だが高コストなグローバルステートをセットアップする
- レンダターゲット操作
{バインド|クリア|解決|逆圧縮|…} - グローバルステート(フレーム/ビューレジスタ)
- レンダターゲット操作
フレームサブミッション
- 高レベルサブミットコードは複数パスを実行する
- 不定期だが高コストなグローバルステートをセットアップする
- 各パスはサブミットビュー指示を呼び出す
- 単純でクロスプラットフォームAPI
- そのビューに対するGPUコマンドを生成する
フレームサブミッション
- 高レベルサブミットコードは複数パスを実行する
- 不定期だが高コストなグローバルステートをセットアップする
- 各パスはサブミットビュー指示を呼び出す
- 各サブミットビュー指示はサブミットビュージョブをセットアップする
- ジョブ化規則はプラットフォームごとに変化する
GPUワークのパイプライン化
GPUワークのパイプライン化
フィルタビューサブミット
フィルタビューサブミット
フィルタビューサブミット
高レベルサブミット制御
- クロスプラットフォームなコードパス
- フェーズにまとめられる
高レベルサブミット制御
- クロスプラットフォームなコードパス
- フェーズにまとめられる
- 必須パス
- シェーディングパス
- トーンマッピング/リゾルヴ
- …
- レンダステージサブミッション指示
- 必須パス
高レベルサブミット制御
レンダステージメカニズム
- 実行時サブミッションパスのフィルタリング
- 実行時シェーダテクニック管理を提供する
- 適切な時に適切なテクニックを選択する
- 可視リストのフィルタリングを許可する
- 適切なテクニックを用いる適切なパスにサブミットするための適切なメッシュのセットを選択する
- キャッシュコヒーレンシーと高速なイテレーションのために構築される
レンダステージ
- 各レンダオブジェクトは任意の時にレンダステージを購読できる
- ステージのためのコアレンダラに登録することで
- 登録時、または、実行時の任意の時に
レンダステージ
- 各レンダオブジェクトは任意の時にレンダステージを購読できる
- 可視リストは購読されたレンダステージのみにフィルタリングされる
レンダステージ
- 各ビューは実行時にレンダステージへ購読できる
レンダステージ
- 各ビューは実行時にレンダステージへ購読できる
レンダステージ
レンダステージ
レンダステージによるビューのフィルタリング
- レンダオブジェクトサブミットは以下の両方が真である場合にのみ呼び出したい
- ビューが与えられたステージへ購読している
- ビューはそのレンダステージを購読する可視ノードに制約される
透過が欲しい!
- シェーダのメタデータはシェーダコードでレンダステージを指定する
透過が欲しい!
- オフラインのインポート中に各オブジェクトに対して
- すべてのメッシュにまたがり以下を繰り返す
- すべてのシェーダを見つける
- 抽出レンダステージを割り当てる
- ルックアップコンテナを作る
- すべてのメッシュにまたがり以下を繰り返す
- O(n)かかるレンダステージメッシュの実行時ルックアップ
- 実行時にレンダステージに対するドローコールの高速フィルタリング
ステージ毎サブミットノードのポピュレート
- レンダステージによって可視リストをフィルタリングする
ステージ毎サブミットノードのソート
- 特定のパスはレンダリングに適切なソートを必要とする
- 等価物、など
- ステージ毎準備の間にソートする
- レンダステージ毎に構成可能なコールバックを用いる
- 各レンダノードは実行時に独自のステージ毎ヒューリスティックを計算する
ステージ毎サブミットノードのソート
- 各レンダオブジェクトはひとつまたは複数のサブミットノードを持つことができる
- インライン化された高速ソート --- 極小のソートキーに基づいて
キャッシュコヒーレンシのメモ
- すべてのコアレンダラジョブはレンダノードで操作する
- 小さなデータ構造
- 高速でキャッシュコヒーレントなソート、割り当て、すべてのコアワークロードに対するトラバース
- タイトなイテレーションループ中のより高速なキャッシュされたアクセスのための小さなデータ重複
ステージ毎サブミットビュー
ステージ毎サブミットビュージョブ
- 個別のサブミットリーフジョブに分ける
- ステージ毎 | ビュー | フィーチャ | ノードのセット
- 分割を動的に負荷分散する
ステージ毎サブミットビュー
- 各フィーチャサブミットエントリポイントごとに、ローカルとレンダオブジェクトデータのみにアクセスする
- レンダノード
- フィーチャレンダオブジェクトキャッシュ
フィーチャレンダラのサブミットカーネル
- 各フィーチャごとのコヒーレントなコードパス
- フィーチャタイプでソートできる
- それを許可するパスで
- サブミットビュージョブは合理化されたカーネルプロセサである
完全にジョブ化されたレンダラ
完全にジョブ化されたレンダラ
完全にジョブ化されたレンダラ
完全にジョブ化されたレンダラ
まえがき
- 粒度の粗い並列化
- Destinyのレンダラの目標
- シミュレーションとレンダリングの分離
- コアワークロードのジョブ化
- データ駆動レンダリングサブミッション
- 高度な最適化
- おわりに
レンダジッタ&レイテンシ削減
- マルチスレッド化したサブミット
- GPUバブルを減らすためのCPU/GPU負荷分散
- 効率的なコマンドバッファフラッシュ
- 非同期スワップ
- レイテンシの自動回復を伴うジッター削減
GPU占有率ファクタ
- 究極的な目標: GPUを完全に飽和させ続ける
- アイドルにしない
- 重要なファクタ
- GPUコマンドをGPUにフラッシュするときの時間
- ドローコール
- コマンドリスト実行指示
- レンダターゲットステート
- 前フレームのGPUでの処理が完了したらフリップする
- GPUコマンドをGPUにフラッシュするときの時間
GPUアイドルの追跡
- カウンタはムラがあるが、いくつかのプラットフォームで利用可能
- 追跡するのに役立つようエンジンに組み込んだ
- すべてのパフォーマンスとアクティビティテスト用
- GPUアイドル == 飢餓状態のGPU
- GPUが飢餓状態にあるので、レイテンシがより高くなる一点に役立つ
GPU飢餓の例
GPU飢餓の例
GPU飢餓の例
GPU飢餓の例
- 大量のGPUアイドルバブル
- GPU飢餓を示す
- 全体のレンダリングレイテンシを増加させる
- アカン
GPUを占有し続ける
- マルチスレッド化したサブミット
- 効率的に生成されたコマンドバッファのフラッシュ
- サブミットジョブ粒度の負荷分散
- VBlank同期
VBlankへの同期
- vblankに基づいて計算をずらす
- インターバルを逃さずにフリップできるようなったときにGPU処理が完了させるため
- いくつかのコンソールは生のvblankイベントの追跡ができる
- 完了の制御にとても良い
- その他のコンソール/PCはそのようなものは無く、手動で行う必要がある
- 別のスレッドをスピンさせて、vblankイベントを待つ
- リサイズ/フルスクリーンイベントが送られたときのプレゼントがデッドロックしないようにする
最適なGPU占有
最適なGPU占有
最適なGPU占有
最適なGPU占有
マルチスレッド化したサブミット
- 複雑なトピックであり、すべての側面を網羅できないだろう
- 高レベルサブミットスクリプトは各ビュー毎、ステージ毎サブミットごとにコマンドバッファを生成する
- GPUは順番にコマンドを実行しなければならない
- コマンドバッファをGPUにフラッシュするときに重要
- それらを生成するときではない
高レベルレンダサブミッション
- 高レベルで高コストなレンダステートを発行する
- ターゲットのバインド/クリア/リゾルブ
- 高レベルサブミッション指示を発行する
- “<レンダステージ>に対して<ビュー>をレンダリングする {任意: 更なるフィーチャフィルタ}“
高レベルレンダサブミッション
- 高レベルで高コストなレンダステートを発行する
- ターゲットのバインド/クリア/リゾルブ
- 高レベルサブミッション指示を発行する
- “<レンダステージ>に対して<ビュー>をレンダリングする {任意: 更なるフィーチャフィルタ}”
- このレイヤーの下でジョブ化する
- この指示に対してコマンドバッファを生成する
- “<レンダステージ>に対して<ビュー>をレンダリングする {任意: 更なるフィーチャフィルタ}”
サブミットジョブの生成
- Nコのノードを一緒にジョブ化する
- フィーチャやレンダステージでグループ化する
- サブミットジョブの生成順にコマンドバッファを分ける
- ハイレベルサブミットジョブから
- GPUの順序実行のため
- 各フィーチャ毎|ステージ毎|ビュー毎サブミットジョブ == コマンドバッファ1つ
サブミットジョブ&GPU占有率
- CPUとGPU負荷の最適なバランスを必要とする
- 低CPU負荷で素早くGPUにフラッシュする
- 次に、高GPU負荷を伴う中CPU負荷
- 他
- レンダフィーチャとレンダステージコストでサブミットジョブをソートする
- プラットフォームごとに調整される
- コアレンダラがこのメカニズムを提供する
動的負荷分散
負荷分散レンダジョブ
- ナイーブなアプローチはジョブ化しすぎる可能性がある
- 初期実装: (抽出/準備/サブミット)フェーズあたりフィーチャあたりジョブ1つ
- ワークロードは劇的に変化した
- 例: 環境/地形 --- 抽出|準備でなにもすることがない
- キャラクター{ギア|Skinned|布}: 重い処理
- 多すぎるジョブのオーバーヘッドコスト
動的負荷分散
- 各フィーチャはフェーズ毎コスト間s数を提供する
- スタンドアローンジョブを必須とするか?
- コアシステムはエンティティとフィーチャのフェーズ毎コストに基づいてバッチする
- フレーム内データに基づいて自動的に負荷分散する
ヘテロジニアスジョブ実行
- 各フィーチャはどの実行単位をサポートするかを指定する
- コアレンダラは可用性に基づいて自動的にスケジューリングする
- 例: SPU/PPUスケジューリング
- 大抵は抽出に使われる
- 他のフェーズはPPUでの高負荷処理がある
- 将来的に他のシステムに拡張可能
負荷分散の例
実際には…
まえがき
- 粒度の粗い並列化
- Destinyのレンダラの目標
- シミュレーションとレンダリングの分離
- コアワークロードのジョブ化
- データ駆動レンダリングサブミッション
- 高度な最適化
- おわりに
おわりに
結果
- 複数プラットフォーム間で、低レイテンシで、効率的で、高スケーラブルな実行を届けた
- PlayStation 4、PlayStation 3、Xbox One、Xbox 360
- フィーチャコードはクロスプラットフォーム
- アーキテクチャチューニングに影響を受けない
出荷するのに必要だったこと
- レンダラのマルチスレッド化はDestinyを出荷するためのキーであった
- スケーラビリティと動的負荷分散
- キャッシュコヒーレントなデータアクセス
- ヘテロジニアスのサポート
- 重い処理でも低レイテンシを達成
おみやげ
- データ駆動のパイプライン化されたアーキテクチャはグラフィクスフィーチャを生成することに対して素晴らしい柔軟性をもたらす
- 強固なカプセル化は開発のいたるところで発生する多くの最適化を可能にする
達成した我々の目標
- 出力からゲームステート横断をうまく分離した
- 出荷しているゲームではより良いCPUとGPUの利用率を達成した
- データ駆動アーキテクチャを介してタスクおよびデータ並列なワークロードにレンダリングアルゴリズムを分解することで
LEGOのような拡張性
- 新しいフィーチャの追加は素直である
- 単純で、軽快
- コアアーキテクチャはデータ管理とジョブ化を提供する
- 新しいレンダステージの追加は簡単である
Don’t Rinse and Repeat
- クロスプラットフォームのフィーチャコードはグラフィクスフィーチャコードを一度だけ書けば良いことを意味する
- クロスプラットフォームなサーフェスとシェーダレイヤに依存する
- フィーチャを書き、簡単にプラットフォームに移植する
- 低レベルレイヤ(デバイス、サーフェス、シェーダレイヤ)と1つのフィーチャレンダラを移植する(例: 5週)
- それが終われば、フィーチャの残りを1週間以内で変換する
- 例外: 高度なAPI(ハードウェアインスタンシング) --- 移植するための小さな努力
- 大量の全体エンジニアリングの節約をもたらした
課題
- 各プラットフォームは以下に対して独自の要件を持つ
- サブミットジョブ粒度
- コマンドバッファ生成
- プロジェクトのいたるところにある連続したジョブオーバーヘッドコストの最適化