パフォーマンス向上:VALORANTのグローバル インバリデーション
こんにちは!VALORANTのパフォーマンスチームでソフトウェアエンジニアをしているAaron
Cheneyです。VALORANTの競技の公正さを維持するには、パフォーマンスが重要な要素であり、私たちのチームには、サーバーとクライアント両方のパフォーマンスを監視、維持、改善する責任があります。
今回は、数ヶ月前から開発を進めていた機能、グローバル
インバリデーションに関する最新情報をお届けします。この機能の詳細は後ほどあらためて説明するとして、まずはパッチ4.03以降、この機能がクライアントパフォーマンスにどのような影響を与えたかを見てみましょう。
ネタばれ警告:かなりスゴイです。
グローバル
インバリデーションは、数多くのプレイヤーにクライアント
パフォーマンスの大幅な改善をもたらしました。実際、この機能の導入によるパフォーマンスの向上は、過去最高の結果となっています。
このようなグラフはとても興味深いですし、非常に喜ばしい結果となっていますが、重要なのはこの数値が何を意味しているのかを正確に理解することです。私たちは大量の複雑なデータセットを扱っています。データを整理し、フィルタリングし、コントロールすることで、プレイヤーの体験を理解していくのです。ここでは、その全貌を把握するために必要なポイントを紹介します。
- グラフは「パッチ」に対する「平均FPS」を可視化したもので、数値が高いほど良いと考えられます。
- 各線は、プレイヤーに共通するハードウェア構成(CPUとGPUの組み合わせ)を表しています。パフォーマンスデータを分析する際、この組み合わせが、期待されるパフォーマンスを計るうえで最も重要な予測因子になると考えています。CPUとGPUの組み合わせが同じPCは、このグラフでは一緒に集計されています。
- データサンプルは、アンレートとコンペティティブの2つのキューから取得しました。これらはVALORANTで最も人気のあるゲームモードであるため、ここから得られるデータを理解し、パフォーマンスを向上させることに多大な労力を費やしています。
- 10人ちょうどでない試合は除外しています。こうすることで、異常値によってデータが歪まないようにしています(プレイヤー数が少ない試合の方が、パフォーマンスが高くなるのです)。
グローバル インバリデーションの概要
グローバル インバリデーションを導入すれば、CPUバウンド(CPUによる制限が高い状態、一般的に中~高スペックのPC)のクライアントにおいて、パフォーマンスを最大15%向上させることが可能です。これを実現するために、複数のチームが何ヶ月もかけて取り組みました。最適化が必要な部分を特定するプロセスが功を奏し、リスクを管理することで安定したゲーム体験が可能になりました。
恩恵の対象、そして期待値の調整
私たちのライブ測定基準によると、グローバル
インバリデーションを導入したことで、CPUバウンド(CPUによる制限が高い状態、一般的に中~高スペックのPC)のクライアント構成において、最大15%のパフォーマンス向上が見られました。
なお、総体的に見れば顕著な上昇傾向にありますが、それは瞬間ごとのゲームプレイを表しているわけではありません。加えて、同じハードウェアを持つすべてのPCで、確実に同じ結果が得られるというわけでもありません。
これはつまり、CPUバウンドのPCでプレイした際、VALORANTの基本的なパフォーマンスは概して向上しているものの、実際にどれくらいのパフォーマンスを得られるかは、プレイヤーごとに異なるその他の様々な要因によって決まってくるということです。
グローバル インバリデーションの解説
グローバル インバリデーション自体について解説していく前に、まずUnreal EngineのUI要素について少し説明する必要があります。
ウィジェットとツリー構造
ウィジェットとしても知られるUI要素は、ツリー構造を使用して、より小さなビルディングブロックから作成されます。ツリー構造は、皆さんのPCに使われているファイルシステムのようなものです。ウィジェットは、任意の数の「子」を持つことができます(フォルダにいくつもファイルを入れられるのと同じです)。
これらのビルディングブロックを組み合わせることで、複雑なウィジェットを作成することができます。例えば、弾薬カウンターは多くのブロックで構成されており、そのツリーは次のような形をしています。
これをすべて併せると、弾薬カウンターのビルディングブロックは次のようになります。
(上記の赤色の輪郭は、分かりやすくするために強調して表示しています。実際には、多くの輪郭が重なっています。)
ツリー構造内の1つまたは複数のウィジェットが変更されるたびに、他の複数のウィジェットに影響を与える可能性があります。例えば、あるウィジェットが画面上の新しい位置に移動した場合、その下にあるすべてのウィジェットも同様に位置を再計算する必要があります。これらの変更は、次のセクションで説明する「インバリデーション」というシステムで管理されています。
インバリデーション
インバリデーション(無効化)とは、Unreal
Engineが使用しているメカニズムで、特定のウィジェットが変化した際、更新が必要であることを示す役割を持っています。
ウィジェットは、様々な理由でインバリデーションが必要になります。アニメーション、色、不透明度、サイズ、順序、テキスト、画像、その他多くのプロパティは、ゲーム内で発生していることに応じて、変化する可能性があるからです。このような変化が起こったウィジェットは「インバリデーションされた状態」となり、更新が必要であることが示されます。
このプロセスがさらに複雑にする要因として、1つのウィジェットに複数の種類のインバリデーションが存在できるということが挙げられます。具体的には、以下のような種類があります。
- レイアウト - ウィジェットのサイズが変わった場合(非常に負荷が高い)。
- ペイント - ウィジェットの外観は変わったが、サイズが変わっていない場合。
- 子の順序 - ツリー内でウィジェットの順序が変わった場合(レイアウトも関わってくるので、負荷が高い)。
- 可視性 - ウィジェットの可視性が変化し、不可視または可視になった場合(レイアウトも関わってくるので、負荷が高い)。
これらのインバリデーションの種類は、正しく描画するためにどのような操作をしなければならないかを示すために使用されます。
あるウィジェットが他のウィジェットに依存している場合、さらに複雑化することになります。ウィジェットは階層化されており、そのレイアウトは様々な要素に依存します。1つのウィジェットをインバリデーションした場合、関連する複数のウィジェットをインバリデーションしないと正しく描画されない場合があります。例えば、複数のウィジェットが縦レイアウトで配置されている場合(例:フレンドリストがあるソーシャルパネル)、ウィジェットの順番が変わると(例:フレンドがオンラインになる)、縦レイアウト内のすべてのウィジェットを更新する必要があります。
こうしたシステムには、次のような目的があります。
- できるだけ少ない数のウィジェットをインバリデーションする:これにより、正しく描画するために更新が必要なウィジェットの数を減らすことができます。
- 必要な場合にのみ、ウィジェットをインバリデーションする:不要なウィジェットのインバリデーションが行われると、貴重なCPUサイクルが浪費されてしまいます。
- ウィジェットがインバリデーションされていない場合、結果をキャッシュして、毎フレームで素早く描画できるようにする:何も変化がなければ、CPUサイクルを節約することができます。
ウィジェットがどのように更新され、どのようなことが原因でインバリデーション(無効化)を行うかを理解するには、これで十分でしょう。次に、開発者がどのようにこれを実践しているかを見ていきます。
インバリデーション ボックス
Unreal
Engineには、インバリデーション
ボックス(無効化ボックス)と呼ばれる、複数のウィジェットをグループ化するためのコンポーネントが用意されています。1つのインバリデーション
ボックス内に含まれるすべてのウィジェットは、事前に渡されたり、ティックされたり、描画されたりしません。その代わり、結果は頂点バッファにキャッシュされます。
インバリデーション
ボックス内のウィジェットがインバリデーションされるたびに、キャッシュされたデータは破棄され、ウィジェットは更新されて再び描画されます。キャッシュのリフレッシュは、1フレーム単位では負荷が高いかもしれませんが、長い目で見れば、はるかに優れた清算結果を得ることができます。
特に、リリースに向けて作業していた当時は、高パフォーマンスなUIを実現するうえでインバリデーション
ボックスは重要な役割を担っていました。とはいえ、制限なく利用できるわけではありません。
- 開発者は、インバリデーション ボックスでグループ化するのに適しているウィジェットはどれか、理解する必要があります。定期的に更新されるウィジェットは適していません。
- インバリデーション ボックス内のウィジェットの配置は、開発者が手作業で行うことになります。ゲーム内の全ウィジェットに対してこれを行うことは不可能であるため、開発者はどのウィジェットがインバリデーション ボックスに入れる価値があるのか、理解する必要があります。
インバリデーション
ボックスの詳細については、Epic
Gamesのドキュメンテーションをご覧ください。
これで、グローバル
インバリデーションの解説に必要な説明は十分できたと思います!
グローバル インバリデーションの詳細
ここまでお話ししたところで、「すべてのUI要素をグローバル
インバリデーションボックスに入れればいいのでは?」と思われるかもしれません。それこそ、グローバル
インバリデーションが(事実上)行っていることになります。
グローバル
インバリデーションは、ゲーム全体のUIパフォーマンスを大幅に向上させるとともに、開発者が個々のインバリデーション
ボックスにウィジェットを配置するために必要な手作業を減らすことを目的としています。つまり、良いとこ取りなわけです。
しかし、UE4.25(VALORANTが使用しているUnreal
Engineのバージョン)において、グローバル
インバリデーションはすべてのウィジェットタイプで普遍的にサポートされているわけではありません。Unreal
Engineの新たなバージョンでは改良が加えられていますが、VALORANTですぐにそれを利用することはできませんでした。さらに、グローバル
インバリデーションによってVALORANTがどれだけ速くなるのか、私たちも正確には把握できていなかったのです。
私たちの作業はここから始まりました。
なぜ、この作業を行おうと思ったのか?
2021年7月下旬、チームは社内プレイテストでグローバル
インバリデーションのテストを行うことにしました。いくつか不具合を修正するために小規模な変更を施し、無事にプレイテストを終えることはできました。しかし、プレイテスト中に新たな不具合が出てくることは予期していましたし…実際に不具合は見つかりました。
プレイテストが終わる頃には、ハッキリとしたものだけでも、20個ほどの不具合が判明しました。厳密にテストされていないエッジケースはもちろんのこと、判別しづらく、影響度の高い不具合がこの先で発見される可能性もありました。
では、グローバル
インバリデーションによって、パフォーマンスは向上したのかというと…確実に向上は見られました。
その1回のプレイテストで得られたデータを分析したところ、UIは約35%高速化されていることが判明しました(備考:UIは1フレームにかかるコストのごく一部にすぎません)。
とはいえ、考えるべきことはまだ多く残されていました。
- すべての不具合を修正するには、どれくらいの時間が必要なのか?
- どのチームが作業を担当するのか?
- この作業は、他の予定されている作業よりも優先されるべきか?定期的なコンテンツのリリースに対応するため、チームのスケジュールは数ヶ月先まで決まっていることが多く、今回のようなエキサイティングな作業も含めて、突発的な作業をスケジュールに組み込むのは難しいのです。
- 不具合を修正することで、パフォーマンスの向上が損なわれる可能性はないか?すべてのバグを修正するには多くのコード変更が必要で、どの変更もUIの負荷を高める可能性を秘めています。
- この作業はいつ行うべきなのか?Unreal Engineの将来のバージョンにおいて、グローバル インバリデーションが活発に開発されることは分かっていたため、Epic Gamesからの変更を統合するスケジュールを検討する必要がありました。
最終的にいくつかの理由から、この作業をする価値はあると判断しました。
Unreal Engineの統合とそのタイミング
Unreal
Engine 4.26と4.27では、グローバル
インバリデーションについてかなりの進展がありましたが、VALORANTでは少し遅れた統合スケジュールで作業を行っています。私たちが「ブリーディングエッジ技術」(最先端の先をいく技術)を取り入れないのは、リスクを管理し、プレイの安定性を確保するためです。
ですが私たちのスケジュールでは、Unreal
Engine
4.25を何ヶ月も先まで使い続けることになるので、プレイヤーがこのパフォーマンス向上の恩恵を受けられるのは、1年以上も先になってしまいます。これは納得しがたい事実でした。
VALORANTがUnreal Engineのアップグレードについてどう考えているかについては、VALORANTの技術リードであるMarcus ReidのTwitterスレッド(英語)でご覧いただけます。
明白だったパフォーマンスの向上
グローバル
インバリデーションは、パフォーマンスに関する作業としては実に特殊で、事前にその価値が分かっていました。社内プレイテストにおいて、得られるであろうパフォーマンスの向上幅は測定されており、その数値を提供するための道筋も(ほぼ)明確でした。
パフォーマンスに関する作業は難しいものです。非常に地道で、派手さはありません。漸進的な変更であっても、大抵は時間の経過とともにパフォーマンスを向上させ、1回の変更で2桁の向上を実現できるのは稀です。そうためグローバル
インバリデーションは、棚上げしておくにはあまりにもったいない最適化だったのです。
不具合の修正によって向上幅が減少したとしても、グローバル インバリデーションは、妥当な時間内にプレイヤーに大きな恩恵を提供できる、最良のチャンスだったと言えるでしょう。
どのようにしてこの作業を達成したのか?
グローバル インバリデーションについては、2021年7月下旬に初期テストが開始されましたが、本格的な機能の安定化に向けた取り組みが始まったのは、2021年9月下旬です。
Epic Gamesの変更を選択的に取り込む
Unreal
Engine 4.26と4.27の完全統合は見送られましたが、Epic
Gamesがグローバル
インバリデーションに積極的に取り組んでいることは知っていたので、何千もの変更点を調べ、どの変更を統合すれば、安定して完全に機能するグローバル
インバリデーションに近づけることができるか、特定することにしました。
変更を部分的に統合するのは、困難な作業でした。ここで重要となったのが、Epic
Gamesから適切な変更を取り入れつつ、安定性を維持できるよう、コアエンジンの機能をできる限り変更しないことでした。この作業はすべて、他の開発者に影響を与えないように、メインのVALORANTブランチとは別のブランチで行われました。
Epic
Gamesの変更を選択的にエンジンに統合した後、さらに数週間かけてできるだけ多くの不具合を修正し、VALORANTのメインブランチにグローバル
インバリデーションを導入する準備をしました。その際、万が一の事態に備えて、この機能を素早く有効化/無効化できるようなトグルを作成しました。
多くの不具合が修正され、4.26と4.27でEpic Gamesによって行われた変更の多くが反映されたところで、孤立したブランチをメインブランチに統合しました。
不具合間の共通点を見つける
グローバル インバリデーションに関する不具合の多くは異なる形で現れましたが、根本的な原因は1つの問題に帰着することが多かったのです。1つの変更で複数の問題を解決できるため、これらは非常に直しがいのある不具合と言えます。実際、1つの変更によって、ゲーム中に散見されていた10個以上の不具合が修正されたケースもあります。根本的な問題を慎重に分析することで、グローバル インバリデーションの信頼性と安定性を向上させる、強固な解決策を導き出すことができました。
不具合の特定、不具合の修正、プレイテスト、そしてその繰り返し
その後の数週間から数ヶ月間は、グローバル インバリデーションを有効化してからプレイテストを行い、一連の不具合を特定し、プレイテスト後はグローバル インバリデーションを無効化して、それらの不具合を修正するという定期的なサイクルが続きました。
このサイクルを繰り返すたびに、報告される不具合は減少しました。報告される不具合の数が大量の状態から少量へ、そして完全になくなるまで、このサイクルを続けました。
2021年11月末までに、主要な問題はすべて解決し、グローバル インバリデーションはほぼ安定しました。
注目すべき不具合
- アストラがゲームをクラッシュさせる - ある時期、アストラを使っていると、試合の読み込み時に必ずクラッシュが発生するということがありました。この不具合は、Epic Gamesからの変更を統合して修正されました。
- 多重継承のクラッシュ - C++において、多重継承は厄介なテーマです。あまり深くまで掘り下げずに話すと、あるクラスのデストラクタが正しい順序で実行されず、これがクラッシュの原因となっていました。この問題は、2行のコードを入れ替えて継承順を変更するだけで解決しました。多重継承の詳細については、こちらのページ(英語)をご覧ください。
- 無限のチャットオーディオ - メニュー画面でチャットバーにカーソルをあわせると、サウンドが再生されます。このサウンドが1秒間に何回も再生されたため、非常に不快な不具合でした。この不具合の修正には、ウィジェットが1フレームに複数回マウスイベントを受信するタイミングを理解する必要がありました。
テスト方法
グローバル
インバリデーションの性質として、特に注意(つまり、徹底的にテスト)しなければならなかったのが、ゲームのあらゆる側面に影響を及ぼすという点です。文字通り、あらゆる側面です。
皆さんのフレンドリストはもちろん、対戦待ちを始めるボタンや設定メニューに至るまで、すべての側面です。私のヘッドショット確率への影響はないかもしれませんが…
つまりここで言いたいのは、UI要素はゲームのいたるところに存在し、プレイヤーに重要な情報を伝えているということです。そのようなUI要素を1つでも壊すことは許されません。
そのため、QA部門はグローバル インバリデーションが意図したとおりに機能しているという確信を得られるよう、複数の戦略でテストプランを作成しました。
バーティカル スライス テスト
VALORANTの「バーティカル スライス」は、クライアントの起動から、対戦待ちの開始、試合全体、試合終了画面の操作まで、プレイヤーが一般的にたどる主な道筋を表しています。ゲームの重要な要素に集中することで、QAはゲームで最も使用される要素を迅速にテストし、問題を早期に発見することができました。
破壊テスト
バーティカル スライス テストが終わると、今度は破壊テストが始まります。この種のテストは多くの場合、外部要因(ネットワークのping、フレームレート、Alt+Tabのウィンドウ切り替えなど)を変更することで、通常とは異なる問題を特定することを目的としています。社内ツールを駆使して、QAチームは数週間かけて破壊テストを行いました。
エッジケース テスト
VALORANTにおける大半の要素は、ごく一部のプレイヤーしか体験することがありません。また、中には一度しか体験できないもの(例:新規プレイヤー契約)もあります。ですが、目に触れにくい部分だからといって、重要度が低いわけではありません。エッジケースをすべて洗い出し、テストすることで、隠れたエラーを発見することができました。
PBE(パブリックベータ環境)テスト
PBEでの実装は、グローバル
インバリデーションにとって大きな節目となりました。
- 一般プレイヤーがこの機能を試すのは、これが初めてでした。つまり、グローバル インバリデーションを「現実の世界」の条件下でテストすることができるわけです。
- PBEには様々なハードウェアの仕様が盛り込まれています。PBEは意図的に低スペックから高スペックまでのPCをカバーしており、多種多様なプレイヤーデバイスでテストを行えるようになっています。これにより、グローバル インバリデーションが幅広いプレイヤーに対してしっかりと機能しているか、確信を得ることができたのです。
2022年1月22日と1月23日の週末にPBEでテストを行った結果、グローバル インバリデーションが整合性を阻害せず、パフォーマンスの向上も予測通りであると確かめることができました。
グローバル インバリデーションのリリース
パッチ4.03でグローバル
インバリデーションをリリースした後、私たちはプレイヤーからの不具合報告を注意深くモニタリングしていました。また、パフォーマンスデータにも目を配り、自分たちの予測と結果が一致していることを確認しました。結果として、グローバル
インバリデーションは大成功となりました。皆さんが改善されたフレームタイムを満喫しているようなら何よりです。
グローバル インバリデーションが実装された今、パフォーマンスチームはさらなる向上に向けた作業に戻っています。それでは、またお会いしましょう!これからもVALORANTをお楽しみください!