恒例のBBQ会!

こんにちわ。Pocolです。
今年もCEDEC恒例のグラフィックスプログラマーで集まってやるBBQ会を開催するそうです!
昨年参加された方は今年もご参加ください!

詳細については幹事のnikqさんのツイートをご参照ください。

果報は寝て待て!

こんばんわ、Pocolです。

昨年に引き続き,グラフィックスプログラマーやTAさんなどでBBQ会を開催するようです。
昨年参加された方は是非奮ってご参加ください。
詳細は,近日中に主催者よりご案内あるそうなので,お待ちください。

そんなわけで,皆で肉喰いましょう!
今年も楽しみだ…。

それではまた!

Steam Deckでのデバッグ

こんにちわ。Pocolです。

Steam Deckでのデバッグ方法について,ググってもあんまりいい情報が出てこなかったので,ここにメモしておこうと思います。

前提

  • Steamの公式ドキュメントを見ておきましょう。
  • Microsoft Child Process Debugging Power Tool をインストールしておきましょう。設定も忘れずに
  • デバッグDLLはSteam Deck実機上に含まれないので,あらかじめ転送するフォルダにデバッグ用のDLLを含めるようにしましょう
  • 開発PCとSteam Deckは同一LAN上になるように接続しておきましょう。
  • Windowsで開発しているものであれば,Proton – Experimentalにするのを忘れないように。

Visual Studioを使ったデバッグ

リモートデバッグを使用して,デバッグを行います。
まず,SteamOS Devkit OSを立ち上げて,実行ファイルやアセットなどを1つのフォルダ下にまとめておきます。
次にUploadを実行する前に下記の設定を行っています。

  • Steam Playにチェック入れる(This title requires Steam Playの項目)
  • Steam Play debugにチェックを入れる(Start Visual Studio C++ debugger service on launchの項目)
  • Wait for attachにチェックを入れる(Wait for a debug client to attach)

チェックを入れてから,Uploadボタンを押して実行ファイルとアセットファイルなどを実機に転送します。
DISMISSにならずにちゃんとアップロードできていたら,Steam Deck実機上からゲームを起動します。
ゲームはSteam Deckのホーム画面から「ライブラリでもっと見る」を選択し,「非STEAM」の項目に居る場合があるので,R1ボタンで切り替えて,デバッグしたいアプリを選択してください。

ゲームが起動したらアタッチ待ちに入るので,Steam Deck上で,Steamアイコンが表示された状態でグルグル回っている画面で止まった状態になります。
この状態で,開発用PC側からアタッチをかけます。
Visual Studioを立ち上げて,リモートデバッグを選択します(「デバッグ」>「プロセスにアタッチ」)。

「リモート認証なしーWindows」を選択して,検索ボタンを押します。
ウィンドウが立ち上がるので,その中からsteamdeckを選択します。
選択するとプロセス一覧が表示されるようになるはずなので,steam.exeを選択します。
前述したChild Process Debugging Power Toolがインストールされて入れば,ゲームにアタッチできるようになります。
アタッチ後は通常のVisual Studioのデバッグと同様に進めていけばよいです。

RenderDocを使ったデバッグ

SteamOS Devkit Client を立ち上げ,Devkitsボタンを押して,下から3番目あたりにある 「RenderDoc captures enabled」にチェックを入れておきます。
チェックを入れたSteam Deck上でデバッグしたいゲームを起動します。
ちゃんと設定がされていれば,画面左上にRenderDocのオーバーレイが表示されるようになっているはずなので,これを確認します。

続いて,開発PCからRenderDocを立ち上げ、メニューから File > Attach to Running Instanceを選択します。
Remote Host Managerというウィンドウが立ち上がるので,Steam DeckのIPアドレスを打ち込み,Addします。
Referesh Allを押して,更新をかけます。ゲームが立ち上がっていれば,win64_preloaderというようなプロセスがあるので,これを選択して Connect to App ボタンを押します。
コネクションが確立されたら,RenderDoc上から,Capture Frame(s) Immediately ボタンを押して,フレームキャプチャーを実行します。
キャプチャーが成功すれば,Captures collected にフレーム画面が表示されるようになります。このデータを一度開発PC上に保存しておきます。
保存の仕方はフレーム画像を右クリックしてポップアップメニューからSave を選択すればファイルダイアログが立ち上がるので,好みの場所に保存しておいてください。

次にフレームデバッグの準備をします。Steam Deck上で,RenderDocのremoteserverを起動する必要があります。
SteamOS Devkit Clientに戻り,Devkitsボタンを押して切り替え,Remote Shell ボタンを押し,ターミナルを起動します。
ターミナルが起動したら renderdoccmd remoteserver -d を打ち込み,RenderDocリモートサーバーを起動させます。
成功すれば,
Spawning a replay host listening on *…
Detaching.
というようなメッセージが表示されるはずです。
次に開発PC側のRenderDocの接続設定を変更します。
RenderDocウィンドウの左下にReplay Contextがあるので,これを接続しているSteam Deckに変更してください。
接続できれば,ステータスバーが Remote server ready というように表示が変わるはずです。

ここまで来たら,あとはいつも通りです。
キャプチャーしたフレームを選択すると,ドローコールなどが見えるようになるので,通常通りにグラフィックスデバッグを進めていきます。

おわりに

かなり設定が面倒ですが,これでデバッグが進められるようになるはずです。
デバッグDLLがなかったり,依存DLLが無いとサイレントでゲームが落ちるようなので,まずはちゃんと起動する状態にするのと,RenderDocのフレームキャプチャーしても落ちない状態に持っていくのが,難所だったりするので頑張ってください。

またカスタムビルド

どうしてもエディタとしてVisual Studioを使いたいPocolです。

一番簡単なカスタムビルドのメモです。
.vcxprojをテキストエディタで開いて,一番下のほうに次ような感じでコマンドを挿入します。

  </ImportGroup>
  <Target Name="Build">
      <Exec Command="call build.bat" />
  </Target>
  <Target Name="Clean">
      <Exec Command="call clean.bat" />
  </Target>
</Project>

ちなみにExecタスクのドキュメントは下記ですので,細かい設定を追加した場合は下記を参考にしてください.
MSBuildリファレンス > タスクリファレンス > Execタスク
https://learn.microsoft.com/ja-jp/visualstudio/msbuild/exec-task?view=vs-2022

Visual Studio上からリビルドを実行した場合は,Clean —> Build の順番で呼び出しされます。
もしリビルド自体をカスタマイズしたいなら

  <Target Name="Rebuild">
    <Exec Command="call rebuild.bat" />
  </Target>

とやってあれば,自前にリビルドコマンドに変更できます。

実行時のコマンドのカスタマイズは,通常のVCと同じようにプロパティから変更すれば良いと思うので,それでやればよいかと思います。
…というわけでカスタムビルドのメモ書きでした。

どっかで

クリックしてtede-msbuild-2.0.pdfにアクセス

とか

あたりを参考にもうちょいちゃんとしたものを今後改造してみたいと思います。

DREDとAftermathのサンプルプログラムを作りました。

こんばんわんわん、Pocolです。

X(旧Tiwtter)でも書いたのですが,DRED(Device Removed Extended Data)のサンプルプログラムを書きました。
サンプルは以下に置いておきました。
https://github.com/ProjectAsura/D3D12Samples/tree/master/D3D12_DRED

DREDなんですが,意外とまともなドキュメントが無いです。ドキュメントあるんですけども,わかりづらい,「この変数の意味は?」みたいな痒いところに手が届くものが無い感じがしますね。(単純に、ドキュメントみて理解できない私がアホなだけなんですが…)
…というわけで,コードを書いてみました。
仕事で使っているのはちゃんと,Push/Popの入れ子とかも考慮しているやつですけども,まぁええでしょ。こまけぇこたぁいいんっすよ。
結局,DREDのサンプルで困るのは「これちゃんとGPUクラッシュ時に出るの?」という所で,故意にGPUクラッシュさせるようなプログラムがなかなかネットで見つからない。
それだと,動作確認に困るので,GPUクラッシュさせるプログラム書きました。
ここ最近,ずっとGPUクラッシュの調査していたので,どうやれば簡単にGPUクラッシュを引き起こせるかなどのノウハウが溜まったので,その知見を活かして書きました。
一番よくある例,実行中にテクスチャを解放しちゃうやつ。これが一番良くあるので,Tボタン押したら,テクスチャをRelease()するようにしました。これで簡単にPageFaultのGPUクラッシュが発生します。TDRはレジストリいじっている場合は,発生までに時間かかるし,意外とGPU側でちゃんと対処してくれちゃったりする場合もあるので,無理やりやろうとしても意外と発生しなかったりします。サクッといかない。
DirectX-Samplesにはhttps://github.com/microsoft/DirectX-Graphics-Samples/tree/master/Tools/DXGIAdapterRemovalSupportTestというやつもあるみたいですが,こっちは触ったことないんで良く分からんっす(詳しい人教えてください)。

…で馬鹿の一つ覚えみたいな感じなんですが,PageFaultを発生できるようになったので,NVIDIA Aftermathのサンプルも書きました。
一応クラッシュ発生時に,ShaderBinaryとShaderPDBを吐き出して,クラッシュログを調査できる感じにしてみました。私のサンプルの場合は,クラッシュが発生している該当シェーダだけを出力するので,そんなにクラッシュダンプ出力に時間はかからないと思います(仕事でつかっているやつは,別の人が既に実装されたやつなんで,全部のシェーダのバイナリとPDBを出力しやがるんで,時間とディスク容量を食いまくって,困るんですよね。直すのは面倒ですし、時間の余裕もないので直す気はサラサラないです)。
サンプルプログラムは下記にあります。
https://github.com/ProjectAsura/D3D12Samples/tree/master/D3D12_NvAftermath
時間があれば,解説書いてもいいんですが,残念ながら,その時間がないのと,若干仕事のせいで鬱気味なのでやる気が起きないっす。(どうせみんなUEやらUnityつかうでしょ?こういう直叩きするひとがもう日本じゃ少数だから,親切に書いてあげても意味が無いんですよ。見る人いないから…)

…というわけで,リリースして精神的に落ち着いたら,のんびりゆったり解説を書こうかなと思います。
まぁ,そんなの期待する人はほぼいないと居ないと思いますが。

GPUクラッシュが激減した…

こんばんわ、Pocolです。

このところ,仕事でずっとGPUクラッシュを追っていたのですが,なんとなくノウハウが溜まってきました。
ほぼ、毎日のようにGPUクラッシュが何十件もあり,色々な人に手伝ってもらいながら,調査していたんですが,ようやくそれがパッタリ収まりました。

いくつか対策を入れていたのですが,結局どれが効いたのかは正直分からないのですが…
groupsharedで,UINT32_MAXでアクセスして,GPU上でメモリ破壊を発生させるコードがあったので,修正した所,謎に発生していたGPUクラッシュが治まりました。
多分,数年レベルで放置されていたバグなんじゃないかと思います。

NVIDIA AftermathとかでGPUクラッシュダンプを調べても,PageFault,さらにシェーダ情報もでない,挙句の果てにはResourceBarrierを実行するとGPUクラッシュする,定数バッファが壊れて無限ループして,TDRで落ちるなど,過去のクラッシュログを見ても,まったく共通性もなく,しかも結構頻発する。でも全然再現性が無い,手元で全く発生しない…という困ったちゃんで,困りまくった挙句何かないのか?と調べてみたら,NVIDIA Aftermathに,GFSDK_Aftermath_FeatureFlags_EnableShaderErrorReportingというフラグがあるのですが,これを有効にしたところ即クラッシュするようになりました。
何で無効だったのか聞いたんですが,こちらはヘッダファイルにも書かれているようにパフォーマンスペナルティがあるということで,ゲームプレイに支障があるとのことで無効化していたとのことでした。
結局,このフラグを有効にしたところ,先ほど述べたgroupsharedのバグを発見できたのと,他にもクラッシュする原因が見つかって,大いに役立ちました。

今後は,確定で発生しないものはGPU上のメモリ破壊を疑った方が良いという知見が得られました。
基本的にはUAVとかSRVとかのLoad()とかoperator []あたりミスっているんじゃないかって思いがちなんですが,これらはAPIドキュメントを見ると、安全に良しなにしてくれそうなことがあるので,メモリ破壊が発生するのは,CPU上での書き換えか,groupsharedのアクセスミスによる2パターンしかないように思えます(他のパターンがあったら教えてください)。CPU上でのメモリ破壊の可能性がほとんど低いことが分かったなら,groupsharedでの破壊が無いかどうかを調べましょう。
今回のバグは,

groupshared g_Variable[XXX][XXX];
みたいなのが定義されていて,
float temp0 = saturate(XXX);
uint temp1 = temp0 * MAX_VALUE – 1;
uint index = min(temp, MAX_VALUE);
g_Variable[index][XXX] = ….;
っぽいような,謎の計算がされていて(uintをマイナス1する時点でぞわぞわしちゃんですが…)
temp0がゼロになったときに,アボンするみたいケースでした。
よくよく見ると「馬鹿か!」って怒鳴りたくなったっちゃうような,不具合なんですが,変数がごちゃごちゃ定義されていたりとか,ジュニアレベルのエンジニアだとこういうチェックがおざなりになりがちなんで,気を付けた方が良いよ!…という良い事例になりました。

…というわけで,社内でも共有したんですが,この場でも共有してみました。
謎バグに困っている方は,groupsharedで変なことしていないかどうか確認してみると良いかもです。
(※ちなみ1個あったら,他にも絶対あるだろうと思って全シェーダをチェックしてみたのですが,確認した所発生しているのは該当シェーダの1個だけでした。)

パンくずリスト

こんばんわ。Pocolです。

最近ずっとGPUクラッシュ調査の仕事をやっていて疲弊しています。
基本的には,DREDとAftermathを使っています。
DREDを使うと,自動でパンくずリストを作ってくれます。
これ,基本的にはマーカー名とどこまで進んだかを教えてくれます。

GPUクラッシュは大半がTDRかPageFaultだと思います。
TDRでよくあるのは,ループ終了条件を定数バッファのメンバとして渡すパターン。
バッファがどっかで壊れて,終了条件値が想定外の値になって,無限ループ扱いになってクラッシュするとかが良くあります。

で、クラッシュ調査時に欲しいのはどのシェーダ?設定されているのはどのバッファ?中身どうなっている?
…あたりの情報が知りたくなります。

そこで,思いました。
DREDの自動パンくずリストをやめて,自前でカスタムのパンくずリストを作った方が便利なのでは?
そう思ったので,パンくずリストについて調査してみます。

Githubやググったんですが,まともな情報がほぼありません。
FidelityFX Breadcrumbs 1.0が唯一信じられるまともな実装です。
これを調査してみます。
ドキュメントは下記です。
https://gpuopen.com/manuals/fidelityfx_sdk/fidelityfx_sdk-page_techniques_breadcrumbs/

ソースコードは

…あたりを見ると良いです。

基本的な仕組みとしては,
D3D12の場合は,WriteBufferImmedidate() で実行済みフラグを立てていくだけみたいです。
Vulkanの場合は,AMD拡張が使える場合は,vkCmdWriteBufferMaker2AMD()やvkCmdWriteBufferMarkerAMD(),そうでない場合はvkCmdFillBuffer()を使って実行済みフラグを立てていくようです。
現在困っているのは,D3D12環境なので以下D3D12として説明します。

で、WriteBufferImmediate()でどこに書き込むか?なのですが,次のような感じで書き込むメモリを用意するようです。
・VirtualAlloc(nullptr, bufferSize, MEM_COMMIT, PAGE_READWRITE)でメモリを用意。— (A)
・(A)で用意したメモリを引数として,ID3D12Device3::OpenExistingHeapFromAddress() をコールして,ID3D12Heapを取得し,CreatePlacedResource()でID3D12Resourceを生成。 — (B)
・(B)に失敗した場合は,CreateCommittedResource()でID3D12Resourceを生成し,メモリはMap()して取る — (C)
・(B)または(C)にてID3D12Resourceが出来上がるので,ID3D12Resource::GetGPUVirtualAddress()して,BaseAddressを取得 — (D)
・(D)で取得したBaseAddressを開始点として,uint(4byte)で,フラグをWriteBufferImmedidate()で書き込んでいく。

これでコマンドリストに記録されるようになるので,あとはクラッシュした際に(B)または(C)で用意してあるメモリをReadしていきます。これで書き込まれていればフラグが立っていくはずなので,どこまでコマンドが進んだかどうかが判定できます。
マーカー名などのデータはCPU側で管理して,Readしたデータと照合して一致させて,デバッグログなどに表示させれば良いようです。

細かい実装は,FidelityFX Breadcrumbsのソースコードを参照してみてください。

…というわけで,パンくずリストを自前実装する際の基本的な仕組みが分かりました。
あとは,CPU側で管理するデータをリッチにしていけば,色々とデータが取れそうです。

まずは、これらの情報を元にカスタムパンくずリストの実装を始めてみようかなと思いました。
そんなわけで、パンくずリストの話でした。
もし、ノウハウを色々とお持ちの方は是非教えてください。

———————
同僚の方にノウハウを教えてもらいました。
メモリが直接見えるコンソール機では,WriteBufferImmedidate()に対応するような命令が無いことが多く,その場合はタイムスタンプを使って,コマンドがどこまで進んだかを調べると良いそうです。
これは確かに良いなと思いました。

本年もよろしくお願い致します。

Pocolです。
喪中につき、新年のご挨拶は控えさせていただきます。
寒い日が続きますが,どうぞ皆様ご自愛くださいませ。
本年もよろしくお願い致します。

パフォーマンス反省点メモ

120FPS対応を仰せつかった,その時のパフォーマンスについての反省点のメモです。

* ComputeSkinningはEarlyDepthと並列で動作させるべき。
非スキニングメッシュをグラフィックスパイプで描画し,その裏でコンピュートパイプでスキニング処理を実行。完了後にグラフィックスパイプに投入できるように変更すべき。つまり,SkinningメッシュとStaticメッシュはコマンドを分けておかなければならない。

* コンテキストロール回避をあらかじめ仕組みとして用意しておく
シェーダのハッシュ値をもとにソートして,コンテキストロールが発生しづらいように設計しておいたほうが良い。PC版DXCでもハッシュ値が公開されていたはずだと思うので,それを調べてソートする感じ。

* プラットフォームによってコンピュートシェーダが速い
ピクセルシェーダを使うとWaveを使いきれないプラットフォームがあるので,フルスクリーン描画系はすべてコンピュートにしておいたほうが良い。これはとあるカンファレンスでも説明されているので,そちらの資料を参照されたし。どの資料かはお教えできません。

* 深度バッファのダウンサンプルを使いまわせる設計にすべき
何度もダウンサンプルすると遅いので,SSAO, RayMarchShadow, SSR等すべてのスクリーンスペースエフェクトで深度バッファを使いまわせるようにあらかじめ設計すべき。さらにいうとAMD SPDとかみたいな実装でダウンサンプル自体も1パスで実行するべき。

* 非同期コンピュートをフル活用できるようにする
できるだけ非同期コンピュートで実行できるようにレンダリングパスを考えるべき。特にフレームの中盤以降にRTVやUAVの依存関係で,動かせるものがなくなる傾向がある。でも,非同期コンピュートは空いているので,この時間を有効活用できるようにフレーム全体を設計すべき。結局使いどころとしては,コンピュートスキニング,VFX,タイルライトの事前処理や,カリング処理関連になるかと…。

* 半透明重い
おそらく次世代機でも半透明が重いのは変わらないと思われる。縮小バッファやTranslucencyLightingVolumeは先に対応しておき,ON/OFFできるように用意しておくのがよろし。Publisherさんによっては,Hardware-VRSは使わないでとか言われるので,Software-Based Variable Rate Shadingを実装しておくのが良いかもしれない。

* FSR重い
開発終盤にFSRパス全体がボトルネックになりやすい。特に深度を作るパスなどは先行して処理することができるので,FSRのパスの一部を切り分けできるようにしておいたほうが良い。また,とある界隈では最適化されたものが配布されているので,そちらをベースにして各プラットフォーム用に動作するようにカスタマイズするのがおススメ。自分で組み込んだ感じだと0.1ms程度通常版よりも高速化した。

* 16bit専用命令を駆使する
レジスタプレッシャー下げや高速化のために,16bitのhalf floatにすることはよくあると思うのだが,普段そこまでカリカリにチューニングしなくても済んでしまっているので,ノウハウがなかったが,できるだけfloatに戻さずに専用命令で演算し続けたほうが良い。単にhalfにするだけで四則演算するだけじゃだめ。ハードウェア命令を駆使するように。先ほど述べたFSRの最適化もこのあたりを使いまくっている。

* wave32のほうが速いケースがある
PS4なんかはWave64モードしかないので,スレッド数が64になるようにコンピュートシェーダとかで組んでいたと思いますが,最近のハードはwave32ベースで作られていて,wave64で実行する場合はエミュレーションされる命令などもあるっぽい。実際にとあるプラットフォームではシェーダコンパイラがちゃんと警告を出してくれて,wave32で実行したほうが良いことを促してくれる。また,Divergenceの多い複雑なシェーダなどではwave32にするだけで,高速化する場合もある。特にフォーワードレンダリングのシェーダなんかは複雑になりやすい傾向があるので,そういうものはwave32にしたほうが良い。またコンピュートシェーダで起動スレッド数が少ないものもwave32モードにしたほうが良い。

* バリア処理はまとめる
いうまでもないがバリア処理はまとめてバッチングするように。過去にカプコンさんだったりの資料で,そうしたほうが高速化するという実例が出ているので,面倒だけどもちゃんとまとめるように。…というかレンダーグラフとかパスグラフみたいなシステムがちゃんと作ってあるなら,そこで吸収するように作ってあるはず。もしつくっていないなら,今すぐにそうなるようにプログラムを組んだほうが良い。

* 何でもかんでもバインドレスにしない!
レンダーターゲットにもテクスチャにも使用されていない的な警告がグラフィックスデバッガで出ることがあるが,バインドレステクスチャとして使っている場合に,使用していることがスルーされることがあるっぽい。実際にあった事例だと,使っていないから消してOK的な指示があるので,遠慮なく削除したところ,見た目がバグることがあった。で、コードを追って調べたところバインドレステクスチャとして使っている箇所だった。なので,基本的にはバインドレステクスチャにしなくてもいいところは無理にしないほうが,ツールのアシストが効くので最適化に役立つことがある。何でもかんでもバインドレスにしないこと!

* 出力シェーダアトリビュートは4つ以下にする
出力パラメータが4つまでなら,処理負荷がかかることはないが,4つを超えると処理負荷がかかって遅くなる。そのため,頂点シェーダやメッシュシェーダからの出力パラメータは可能な限り4つ(float4が4つ)以内に収まるようにパッキングを行う。とあるプラットフォームだけが特定キーワードを付け足すと自動でパッキングしてくれるが,そのような機能が用意されていないプラットフォームもあるのでfloat16_tを使ってできる限り詰め込むと良い。

* 帯域を下げる
R9G9B9E5フォーマットなどを使い,R16G16B16A16_FLOATなどのフォーマットは避ける。R11G11B10_FLOATがあるが,こちらは過去タイトルで絵的なバグが出た事例を聞いたことがあるのと,あんまり精度がよくないため,お勧めできない。前述のR9G9B9E5フォーマットを使うようにする。この際に,アルファブレンドしているものがあると置き換えに苦労するので,デバッグ系描画などはシェーダ上で手動ブレンドして3チャンネルしか使わないようにするなど,あらかじめ最適化を見越した作りにしておいたほうが良い。

* 頂点シェーダは可能な限り使わない
頂点シェーダを使うだけで重いので,できる限り使わない。メッシュシェーダやコンピュートシェーダで処理できるようにしておく。

* コマンドジャンプを使う
奥の手としてコマンドジャンプを使って色々なことを実装することが可能なので,PipelineStateをGPU上で設定したりなど,PCでは出来ない,コンソールならではのことをやって最適化を行う。こうすることによってCPUのドローコールは減らせるので,CPU処理がネックだった場合は高速化ができる。

* バッファのアロケータはちゃんと作る
CommittedResourceはアライメントが64KiBになるので,例えば16Byteの定数バッファをアロケートしても64KiBでメモリが取られてしまうので,小さいバッファ用に自前でちゃんとアロケータは作っておいたほうが良い。作っていないとメモリ周りで嫌な思いをすることになる。
自分が作ったやつだと,VB or IB or (!UAV && ByteAddressBuffer) or (!UAV && StructuredBuffer)の条件を満たして,64KiBなら同一リソースから切り出して使うという実装にした。!UAVにしているのは1個のResourceの場合,リソースステートが部分的にWriteになったりさせることができないので,基本的にはReadしかしない対象に限定するため。これで数百MB減ったケースがある。

* Decompress処理をしないようにする
Decompress処理が走ると重いので,最近のやつだとDecompressしなくていいようにするためのフラグがあったりするので,そいつを付けておく。

* 最初からWave Instrinsicsを使って最適化しておく
終盤に命令最適化すると,バグで困ることが多々あるので,あらかじめ最適はできるところは最初からやっておく。終盤にしかできない場合は,ちゃんとデグレを検知するための単体テスト環境など,機械的なチェック環境を用意しておくこと。人力によるチェックはすり抜けることが当たり前にある。

* GPUネックになりやすいものは一部処理をCPUに逃がす。
今世代でもGPUネックになりやすい。そのため,CPU側に逃がせるものは逃がすようにする。例えば,オクルージョンカリングはソフトウェアラスタライズのもの使うとか,VFXの計算の一部をCPUでやるとか,メッシュのLOD判定をGPUからCPUに移すとか…。

* 非同期解放処理システムを作っておく
同期させるとやっぱりスパイクなどが発生しやすいので,1フレームでの処理対象を制限するか,そもそも大丈夫なように非同期解放の仕組みをつくっておくかしないと,ロード時・ロード直後に痛い目に会うことになる。

* その場しのぎの適当なコードを書かない
w成分空いているからここに高さデータいれちゃえーとか適当な実装をしていると,前述したパッキングによる最適化を行う際に,都合が悪いケースが出てきて,困ることがある。どこにどのデータが入っているかは,誰から見ても分かるようにしておくこと・そしてきちんと管理しておくこと。そうしないとエンバグが発生しまくって,自分の首を絞めることになる。

* ループ処理を見直す
早い段階で,continueできれば重い処理を回避し,最適化できるケースはちゃんと最適化する。メッシュ描画などは何度も呼ばれるため,意外と大したことがない変更でもめちゃくちゃ速くなることがある。

* デバッグツールは用意する
プログラマー的なグラフィックスデバッガーを使いこなせるアーティストは少ないので,ゲーム上にデバッグツール・可視化ツールは用意しておく。大量生産前に用意しないと,そのあとは忙しさで作られることないと思ったほうが良い。特にオーバードローやシェーダの複雑度あたりは必須。