見出し画像

PCゲーム最適化のパラドックス:HAGSをONにしても「表示遅延」が改善されない理由

FPSゲーマーなら設定で一度は通るであろう
WindowsのHAGS(Hardware-accelerated GPU Scheduling / ハードウェアアクセラレータによるGPUスケジューリング)について。
ONがいいのか、OFFがいいのか?

以下ハードウェアアクセラレータによるGPUスケジューリング=HAGS表記.

情報としてお読みください。駄文です。

ここです。

前提としてHAGSは単なるレジストリのフラグではなく、WDDM (Windows Display Driver Model) 2.7以降におけるGPUのメモリ管理とタスクスケジューリングの根本的なアーキテクチャ変更です。

よくあるネットの考え方(AIに聞いてもこんな感じの答えになるはず)

HAGS OFF (従来のモデル)
主体:
Windowsのカーネル(dxgkrnl.sys)がGPUへのコマンド送信、メモリ管理、コンテキストスイッチを主導します。

  • 課題: CPUがGPUのコマンドキューを管理するため、高フレームレート(1000Hzポーリングや高FPS)環境ではCPUのオーバーヘッドがボトルネックになり、レイテンシが不安定になることがあります。

HAGS ON (現在モデル)
主体:
GPU自体(の専用スケジューリングハードウェア)がメモリ管理とコンテキストスイッチを行います。

  • 挙動: OS(CPU)は高レベルのタスクを投げるだけで、細かい並べ替えやVRAM管理はGPUが行います。これによりCPU負荷が減りますが、GPU側の負担(管理コスト)が微増します。

で、今でもONOFF論争はある。「最近はON派がかなり優勢ではあるが」
ネットやAIに聞いても詳細でまともな一次情報は出てこない。

てことで私が出すッ!
くそ細かい面白くない話が続くのでご勘弁を。

まずはHAGSの入り口(System Call)から↓
ゲーム(ユーザーモード)からの命令をカーネルが最初に受け取る「受付カウンター」がNtGdiDdDDISubmitCommand
DxgkSubmitCommandInternal という関数呼び出しが、分岐点となる。

  • プロセスIDの確認 (PsGetCurrentProcess)

  • スレッドの確認 (DXGTHREAD::GetCurrent)

  • エラーチェック (DbgPrintEx 周辺)

これらは「事務手続き」であり、HAGS ON/OFFに関わらず必ず通る道。

分岐↓

「HAGS(ハードウェアスケジューリング)が OFF の時(または非対応の時)」に使われる、古い処理ルート→(DxgkSubmitCommandInternal)

コンテキストのフラグ(0x10 = HardwareScheduled)をチェックして
「このコンテキストはハードウェアスケジューリングされている(HAGS ON)から、この関数(SubmitCommandCb)には渡すべきではない」と判断される。
HAGSをONにすると、この関数(DxgkSubmitCommandInternal)は使われません。

HAGSがOFFの場合(DxgkSubmitCommandToHwQueue) が呼び出されることになります。

HAGSがOFFだった場合のこの後の処理コードは、
排他制御の多用→複数の処理が競合した時、CPUが「信号待ち」をさせられます。これがマイクロスタッター(カクつき)の原因となる。

待機処理(KeWaitForSingleObject)も頻繁→条件が整うまでスレッドを寝かせて待機。FPSゲームにおいて「待つ」という行為は、そのまま入力遅延に繋がる。

メモリ割り当て=ExAllocatePool2(こいつが一番の原因かも)→ゲーム中にカーネルメモリを確保する処理はコストが高いと思う。

HAGS OFF の場合↓

  • ゲームはこの DxgkSubmitCommandInternal を通ります。

  • 結果として、上記のような 「ロック」「待機」「メモリ確保」の地雷原 を毎回走り抜けることになり、CPU負荷が高く、レイテンシがブレやすくなります。

で次はHAGSがONの場合、めちゃ入口が簡素化されている。

DxgkSubmitCommandToHwQueueInternalのみ→これは、OSが「余計なチェックや準備をせず、すぐに処理を内部関数へ投げている」と思っていい。

で中身が面白い。
まずメモリ割り当て (Memory Allocation) がない。

HAGS OFF (さっきのコード) にあったもの

  • ExAllocatePool2PagedPoolArray::AllocateElements という命令があった。

  • 意味: 「OSさん、新しいメモリ領域を確保してください」という命令。

  • FPSへの影響としてこれは非常に重い処理に。メモリの空きを探す時間がかかるため、フレームレートが急に落ちる(スタッター)主犯格です。

HAGS ONにあるもの

  • メモリ確保の命令がありません。

  • コードの行数の多くは、ログ出力 (WdLog...) や プロファイリング (Etw...)、そして「スリープから起こす (TryWakeUpFromD3State)」といった事務処理が主。

  • メモリを確保せず、すでにある「ハードウェアキュー」に命令をポンと置いているだけです。

HAGS OFF (旧ルート)

[ 命令 ] -> [ メモリ確保(遅) ] -> [ リスト作成 ] -> [ ロック(待機) ] -> [ ソフトウェアキュー ] -> [ GPU ]

HAGS ON ルート

[ 命令 ] -> [ ロック(一瞬) ] -> [ ハードウェアキューに直行 ] -> [ GPU ]

「メモリ確保」という、いつ終わるかわからない不安定な要素が排除されているのが最大のメリットになる。

で、ここまでが「命令を投げる処理」です。

ここまでの情報なら答えは1つ
Q. FPSゲーム(VALORANT, CS2, Apex)ではON/OFFどっち?
A. 基本的に「ON」一択。

  • コードから「動的なメモリ割り当て」と「複雑なコンテキスト管理」が排除されており、CPUの処理時間が短く、バラつき(ジッター)が少ないため。

  • 最近のFPSタイトル(特にDirectX 12世代やDX11の最新パッチ適用済み)は、このハードウェアキューを使うことを前提に最適化されているから。

  • 効果としては↓

    • 最高FPS: 大きくは変わりません(GPU性能依存のため)。

    • 最低FPS (1% Low):改善します。 激しい撃ち合いのシーンで、CPUがメモリ管理に詰まることがなくなるためです。

OFFにすべき唯一の状況

KeReadStateEvent や TryWakeUpFromD3Stateがワンチャン 。
HAGS ONの場合、GPUの管理をOSが手放しているため、「VRAMがパンクした時」や「GPUがスリープから復帰する時」の挙動制御が雑!!!

  • VRAM使用率が常に95%を超えるようなギリギリの配信設定をしている場合、HAGS ONだとOBSがカクつく可能性がある。

  • それ以外(純粋にゲームプレイのみ)なら、ON が最強です。

で次は上記処理をVRAMに読み込む命令を見てみる。
HAGSがONで話を進めます。

Windowsのグラフィックスメモリ管理の中枢を見ていくとWithWaitが出てくる。

HAGS(ハードウェアスケジューリング)の売り文句は「非同期で高速」なはず。しかし、VRAMのページング(データの転送)に関しては、OSが「待機(Wait)」を強制している箇所が残っている。

  • 挙動がVRAMにテクスチャを転送する際、もしGPU側のキューが詰まっていると、この関数は完了するまでスレッドを停止(ブロック)させる

  • ゲームへの影響として↓

    • VALORANTで「角を飛び出した瞬間」や「スキルを使った瞬間」に新しいエフェクト(テクスチャ)を読み込もうとする。

    • その瞬間、この WithWait が発動し、GPUへの命令が詰まると、CPU側のゲーム処理が一瞬停止します。

    • これが 「敵が見えた瞬間に0.1秒固まって負ける」 現象の内部ロジックです(別にこれだけが原因じゃないけどね、なりえるって話です)

↑は「キューがいっぱいなら、空くまでCPUを寝かせて待つ」、あるいは「送信が確実に完了したことを確認するまで次に進まない」という同期処理を含んでます。

動的なメモリ確保の処理。
DMAパケット(メモリ転送用の小包)を動的に確保
している。

  • なぜ重く?

    • ゲーム中に「メモリ確保 (malloc的な処理)」を行うのは、リアルタイム処理のタブー(知らんけど)

    • もしシステム全体のメモリが断片化していたり、負荷が高かったりすると、この「確保」だけで数ミリ秒〜数十ミリ秒かかります。これもスパイク(フレームタイムの跳ね上がり)の原因になりえる。

ロック処理がある
排他制御です。HAGS ONであっても、ページング(メモリ管理)のフェーズでは、OS側が 「ちょっと待って、今台帳書き換えてるから!」 と他の処理を止めている。

  • 高FPS(300fps以上)を出そうとして大量の描画コマンドが走っている時、このロックがボトルネックになり、CPU使用率が跳ね上がる。

なぜHAGS ONでカクつく場合があるのか?

  1. 描画命令 (dxgkrnl)→HAGS ONなら「ハードウェアキュー」へ直行するため爆速。CPU負荷も低いです。

  2. メモリ管理 (dxgmms2)→しかし、裏で動く「データの読み込み」は、今回見つけたように WithWait(同期待機)や Allocate(動的確保) を使っています。

この「速度差」が事故のもと。
描画命令(GPU)が速すぎて、メモリ管理(OS)が追いつかず、WithWait で無理やり同期を取ろうとした瞬間に、ゲームが盛大にカクつくんじゃないかと(ゲームやPC環境に依存するけど、ONにしたら変なバグや特定ゲームがカクつくはよく聞くよね)

でここまでの状況で考えると↓
ハイスぺPCを持っている場合 (RTX 4090 + 最新CPUみたいな)

  • 設定:HAGS ON

  • マシンスペックの暴力で Allocate や WithWait の遅延をねじ伏せられます。描画の軽さ(低CPU負荷)のメリットが上回ります。

  • ただ逆に描画処理は爆速だが、テクスチャ転送(ページング)の同期である WithWait がボトルネックになる場合も。

  • 症状としてGPUは元気なのに、OS側の同期待ちのせいでCPUの1コアだけ使用率が跳ね上がり、FPS値は高いのに画面がガクガクする現象が起きます。

VRAMがギリギリ、またはCPUが少し古い場合

  • 設定:HAGS OFF

  • →今回見つけた WithWait による同期ズレのリスクが高すぎます。OFFにして、OSに全体を管理(交通整理)させた方が、ピークFPSは下がっても「スタッター」は減る。

それでもHAGS ONを使いたい場合 (DLSS 3使用など)

  • 対策→ゲーム内の 「テクスチャ品質」を下げてください

  • 根拠→テクスチャサイズを小さくすれば、今回解析した VidSchiSubmitPagingCommand(ページング処理)が走る回数と転送量が減り、WithWait に引っかかる確率を物理的に下げられます。

「HAGS ONは描画(Rendering)は早いが、準備(Paging)に不安要素がある」ゲーム設定などで「テクスチャ品質」や「マテリアル品質」を下げることが、HAGS ON環境下では特に有効。
なぜなら転送するデータ量と回数を減らせば、この関数が呼ばれる頻度が減り、事故率が下がるからです。
こんな感じの結果になると思う。

でさらに上の処理を次は表示遅延とフリップ(Flip / Present)
「描画した絵を、いつモニターに出すのか?」に移す。
FPSゲームの「入力遅延(Input Lag)」に直結する「Present(画面への描画反映)」の処理はどうなっているのか・・・

でここパンドラの箱なんだけど

結論から言うと、、HAGS(ハードウェアスケジューリング)専用の「高速ルート」への分岐が存在しません(特大鼻くそ)

ONだろうとOFFだろうとおんなじルートを通るんです。

メモリ確保→コンテキストのロック→履歴トークンの作成(こいつはWindowsのデスクトップコンポジション(DWM)との連携用だからDWMを最適化すると緩和される)

つまり、どういうことか?

  • 描画 (Rendering): HAGS ONなら爆速。CPUを介さずGPUへ直行。

  • 表示 (Present/Flip):HAGS ONでもOFFでも、昔ながらの遅いルートを通っている。

これが、「HAGSをONにしても、体感の表示遅延(Display Latency)が劇的には変わらない」 あるいは 「相変わらずスタッターが消えない」 理由の正体だと思う。
「描画命令」の高速化には成功しているが、「画面への反映」の高速化までは(少なくとも今現在のWindowsでは)実装しきれていない、というのが真実。(MiracastやVRヘッドセットなど)」用の分岐は存在する、だが通常のモニター出力には関係ありません。
やはり、通常のFPSゲームプレイにおいては「特別扱いなし」

でここまで見てみると

もし「入力遅延(マウスを動かしてクロスヘアが動くまで)」を最優先したいなら、NVIDIA Reflex + HAGS ON がベストだと思う。
ReflexがHAGSの「描画の速さ」を活かしつつ、今回見つかった「Presentの遅さ」の影響をレンダリングキューの管理でカバーしてくれるから。

もし「スタッター(カクつき)」を絶対に許せないなら、HAGS OFF が無難かも。Present の処理が重いのは変わりませんが、VRAM管理の同期ズレリスクが減るため、フレームタイムは安定する。

でもう一つ「HAGSをONにするとマウスが滑る(操作感が浮つく)」という現象がある&結構報告されている。

これは、GPUからの割り込み処理を行うDPC(遅延プロシージャコール)の中にあった。

KeAcquireInStackQueuedSpinLockAtDpcLevel(
    (PKSPIN_LOCK)(pDpiFdoExtension + 0x5D0), // 
    &LockHandle // 
);

こやつが「操作感の悪化(Floaty Mouse)」の主犯かも。

ここで使われているSpinLock は、「この処理が終わるまで、他のCPUコアは手を出すな」 という強力な排他制御。

  • HAGS OFFの場合

    • GPUの管理はOS主導なので、割り込みの回数は制御されています。ロックの頻度も常識の範囲内。

  • HAGS ONの場合

    • GPUがハードウェアキューで高速にタスクを回すため、「終わったよ!次!」という報告(割り込み)が猛烈な頻度で発生

    • そのたびに、この SpinLock が発動します。

なぜマウスが滑るのか?

マウスの入力処理(mouhid.sysなど)もまた、DPCを使って処理されるため。
GPUのDPCがこの SpinLock を頻繁に(例えば1秒間に数千回)取得していると、マウスのDPCが「割り込みたいけどロックされてて入れない!うんちぶりゅ」という待ち時間が発生します。

これが、0.5ms〜2ms程度の遅延となって、人間の感覚として「マウスがワンテンポ遅れる」「氷の上を滑っているようだ」という感覚(Floaty)に繋がるんだと思う。

外部ドライバへの丸投げ

ここで dxgkrnl.sys は、実際の処理を 外部(NVIDIAドライバなど) に丸投げしています(関数ポインタ +0xc0 への呼び出し)
WindowsはこのDPCの中で「ロックを掛けて」「時間を測って」「NVIDIAドライバを呼ぶ」ことしかしていません。つまり、NVIDIAドライバ側の処理が重ければ重いほど、ロック時間が長引き、マウス遅延が悪化する形になる。

で、監視機能↓

// 処理の前後で高性能カウンタ (QPC) を使用して時間を計測し、ログバッファに記録する。

// ... (処理開始前のQPC記録は省略)

// --- ロック取得・GPUドライバ呼び出し・ロック解除 ---
// **ここに実際の処理(GPUドライバ呼び出し)が入る**

// DPC処理が完了した直後、高性能カウンタを再取得
v_DpcCompletionTime = KeQueryPerformanceCounter(NULL);

// リングバッファのインデックスを更新
// (lVar1 + 0x5d8) はリングバッファの現在のインデックスを格納している場所(ポインタ)
*(DWORD *)(pDpiFdoExtension + 0x5D8) = 
    (*(DWORD *)(pDpiFdoExtension + 0x5D8) + 1) & 0x3F;

// ... (中略: リングバッファへの書き込み)
// RingBuffer[CurrentIndex].EndTime = v_DpcCompletionTime;
  • 処理にかかった時間を計測し、64個分の履歴(& 0x3f = 63)をリングバッファに保存。

  • Microsoftも「ここの処理時間は重要だ」と認識しており、遅延解析ツール(LatencyMonなど)が拾えるようにログを残している。

で、対策としてHAGS ON時のマウス操作感の問題は「DPCのスピンロック競合」でほぼほぼ確定。 これを踏まえた、理論的に正しい対策は以下の通りになる。

MSIモード (Message Signaled Interrupts) の徹底

コードはCPUコアごとのロックを行っています。 MSI Util v3 などを使って、GPUを「LineBased」ではなく「MSI」にし、さらに「Interrupt Priority」を「High」に。

  • これにより、割り込み処理が特定のコアに集中しすぎるのを防ぎ、ロックの待ち時間を減らせる可能性があります。詳しくはディスコードにいろいろあるし、質問したら教えてくれる方いるよ「多分」

CPUアフィニティ(Affinity)の分離

これが最も効果的だけど、くそムズイ、稀にディスコードサーバーにいる方が講座してくれる&時間あったら指南してくれます(多分)

  • GPUの処理(このDPC)を担当するCPUコアと、マウスのUSB処理を担当するCPUコアを物理的に分けてください。

  • 例えば、 Core 2 をマウス専用、Core 4,5 をGPU用にするなど。物理的にコアが分かれていれば、このSpinLockの影響を受けずにマウス信号を通せます。

どうしても嫌ならHAGS OFF

この KeAcquireSpinLock の実装自体は、Windowsの仕様なのでユーザーには変えられない。 もし上記の対策をしても「ヌルヌル動くけど弾が当たらない」と感じるなら、それはあなたの感覚が正しくて「HAGSによるロック回数の増加に、CPUのシングルスレッド性能が追いついていない」証拠。その場合はOFFが正解です(体感大事)

ここまでで、以下のことが分かりました。

  1. 描画: SubmitCommand はHAGS専用ルートがあり高速。

  2. 表示: Present はHAGS専用ルートがなく、従来通り(ボトルネックの可能性)

  3. 管理: Paging(VRAM転送)には Wait(待機)があり、スタッターの要因になる。

  4. 操作: Dpc(完了報告)には SpinLock(ロック)があり、マウス入力と競合して浮遊感を生む。

HAGSのONOFFどちらがいいか・・・結果はON!!!(俺はね)
HAGSをONにする理由は、「描画命令の根本的な高速化」が、その他のすべての遅延(Present, Paging, DPC)よりも遥かに大きなメリットを持つから=SubmitCommandの高速化は、CPUバウンドなゲームでFPSの土台を安定させるという意味。

ただしMSIモード (Message Signaled Interrupts) の徹底はしといた方が良いです。
できるならCPUアフィニティ(Affinity)の分離も、しとくとデメリットを下げられる。
ディスコードでたまにある講座(無料ですが、やってくださる方が暇な時間がある時だけの稀な突発イベントです)で教えて貰うか、適当なYOUTUBE動画やディスコード内のテキスト情報で試行錯誤するか・・・


いいなと思ったら応援しよう!