0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

1フレーム16.7msの内訳を数える — 入力から表示までの遅延を分解する

0
Posted at

1フレーム16.7msの内訳を数える — 入力から表示までの遅延を分解する

「遅延が大きい」という言葉は、対策の役に立ちません。どこで何ミリ秒使っているかが分からないと、削る場所を決められないからです。

60fps のゲームで 1フレームは約16.7ミリ秒。この長さを基準に、入力から表示までに挟まる処理を分解します。

e-Sports TODAY

→ 競技シーンと技術の話題を追うなら e-Sports TODAY

基準となる長さ

まず、フレーム時間を確認します。

const frameMs = (fps) => 1000 / fps;

frameMs(30);   // 33.3
frameMs(60);   // 16.67
frameMs(120);  // 8.33
frameMs(240);  // 4.17

60fps の 16.7ms が、遅延を測るときの単位になります。「2フレーム遅い」は約33msということです。

この単位で考えると、後述する各項目が「何フレームぶんか」で比較できるようになります。ミリ秒のまま扱うより、影響の大きさが直感的になります。

入力から表示までに挟まるもの

ボタンを押してから画面が変わるまでを、順に並べます。

# 区間 性質
1 入力デバイスのスキャンと転送 デバイス依存
2 OS / ランタイムでの入力受け取り 実装依存
3 次のフレームの入力読み取りまでの待ち 最大1フレーム
4 ゲームロジックの処理 実装依存
5 描画とバッファのスワップ パイプライン段数に依存
6 ディスプレイの走査と応答 機器依存

ここにオンライン対戦なら通信が加わります。

# 区間 性質
7 自分 → サーバ/相手 の片道 回線・距離依存
8 相手側での処理 相手の環境
9 相手 → 自分 の片道 回線・距離依存

1〜6 は自分の環境だけで閉じますが、7〜9 は相手と回線が絡みます。 この違いが対策の方針を分けます。

3番が見落とされやすい

上の表で見落とされやすいのが3番、次のフレームの入力読み取りまでの待ちです。

入力はフレーム境界でまとめて読まれます。フレームの直後に押せば、ほぼ1フレームぶん待たされます。直前に押せば、ほぼ待ちません。

// 平均待ち時間 ≒ フレーム時間の半分
const avgInputWait = (fps) => 1000 / fps / 2;

avgInputWait(60);   // 8.33ms
avgInputWait(120);  // 4.17ms

これは実装の不備ではなく、離散的にサンプリングしている以上避けられない待ちです。フレームレートを上げると、この待ちが機械的に半分になります。 描画品質のためではなく遅延のためにフレームレートを上げる、という判断はここから来ます。

通信は往復で数える

対戦相手の入力が自分の画面に反映されるまでは、片道ではなく往復に近い時間がかかります。

const rtt = 40;               // 往復 40ms と仮定
const framesLate = rtt / frameMs(60);   // 約 2.4 フレーム

往復40msは、60fps でおよそ2.4フレームぶんです。相手の動きが2フレーム以上遅れて見えているという状態を、そのまま表示すると操作感が破綻します。

ここで採られるのが、遅延を隠す仕組みです。

遅延を隠す2つの方針

入力遅延を挟む方式は、自分の入力も相手のぶんが届くまで待ってから反映します。全員が同じタイミングで同じ状態を見るので、状態のずれが起きません。代わりに、自分の操作にも常に遅れが乗ります。

予測して巻き戻す方式は、相手の入力を予測してひとまず進めます。実際の入力が届いた時点で予測が外れていれば、その時点まで巻き戻して再計算します。自分の操作は即座に反映されますが、巻き戻しの瞬間に表示が飛びます。

// 巻き戻しの概形
if (受信した入力.frame < 現在のframe) {
  状態を 受信した入力.frame の時点まで戻す
  その入力で再適用
  現在のframe まで再シミュレーション
}

再シミュレーションを1フレームで終える必要があるため、ゲームロジックは決定的で、かつ十分に速くなければなりません。 浮動小数点の扱いや乱数の種を含めて、同じ入力から同じ結果が出ることが前提になります。

この2つの選択は、ジャンルによって向き不向きが分かれます。どちらが優れているという話ではなく、何を犠牲にするかの選択です。

削れる場所と削れない場所

分解すると、対策の優先順位が決まります。

区間 削れるか 手段
入力デバイス △ ポーリングレートの高い機器
フレーム待ち(3) ○ フレームレートを上げる
ゲームロジック(4) ○ 処理の最適化
描画パイプライン(5) ○ バッファ段数を減らす設定
ディスプレイ(6) △ 応答速度・リフレッシュレート
通信(7〜9) × 物理距離は縮まらない

通信の物理的な下限は削れません。 サーバの配置を変えることはできても、光の速度は変わりません。だから予測と巻き戻しのような、隠す仕組みが必要になります。

逆にいえば、1〜6 は自分で削れる範囲です。ここを詰めずに回線だけを責めるのは、順序が逆になります。

測るときの注意

分解して数えるには、区間ごとの計測が要ります。

  • 端から端までの実測(入力から画面変化まで)は、外部の計測手段が要る
  • アプリ内の計測は 3〜5 の範囲しか見えない
  • RTT の平均値だけでは足りない。 ジッタ(ばらつき)が予測の失敗率を決める

平均RTTが同じでも、安定している回線と揺れる回線では体感がまったく違います。巻き戻しの発生頻度が変わるからです。平均だけでなく分布を見る必要があります。

まとめ

コラム

  • 60fps の 1フレームは約16.7ms。この単位で全部を数える
  • 入力から表示までは6区間、対戦ならさらに通信の3区間が乗る
  • フレーム待ちの平均はフレーム時間の半分。フレームレートを上げると機械的に減る
  • 通信の物理的な下限は削れない。だから隠す仕組みが要る
  • 入力遅延方式と予測・巻き戻し方式は、何を犠牲にするかの選択
  • 巻き戻しには決定的なロジックが前提
  • RTT は平均だけでなくジッタを見る

なお、この「16.7ミリ秒の攻防」を扱ったコラムのほか、競技を支えるハードウェア、AIはeスポーツをどう変えるか、チートとインテグリティといった技術寄りの記事が e-Sports TODAY に並んでいました。競技シーン側の文脈と合わせて読むと、どの遅延がどれだけ勝敗に効くのかの感覚がつかめます。

本ページはプロモーションが含まれています

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?