[戻る]

Windows の vsync 問題

注)2001/08/15 時点の情報に基づいた内容です。現状とは合致しない可能性があります。
Windows 上でゲームを作る場合にぶち当たる vsync 問題についてまとめます。

用語と基礎知識

  • ゲームのフレームレート

  • フレームレートとは、 1 秒間に何回ゲーム画面が更新されるかを示す数字です。 秒間フレーム数を意味する frame per second の頭文字をとって、 fps と言う単位が使われます。

  • モニターのリフレッシュレート

  • モニターの表示更新は、走査線と呼ばれるラインが、 画面全体を走査することで行われます。 リフレッシュレートとは、 1 秒間に何回走査が行われるかを示す数字です。 単位は Hz です。

  • vsync

  • ゲームのフレームレートとモニターのリフレッシュレートがずれていると、 ゲーム画面の更新がガクガクと不安定になってしまいます。 通常ゲームソフトではこれを避けるため、 ゲームのフレームレートをモニターのリフレッシュレートに同期させて、 安定した画面表示を行います。 この手法を vsync と呼びます。 「垂直同期」や「V 同期」と呼ぶ人もいます。

モニターのリフレッシュレートの問題点

  • モニターのリフレッシュレートは環境によって異なる

  • モニターのリフレッシュレートは、ユーザーの設定次第で変わってきます。 大抵は 60Hz、高いものだと 120Hz 以上のものもあります。 vsync を行っているソフトを、60Hz の環境と 120Hz の環境で実行する場合、 実行速度に 2 倍の差が出てしまいます。 これでは、ゲームバランスはめちゃくちゃになってしまいます。

  • モニターのリフレッシュレートをソフト的に変更できるとは限らない

  • モニターのリフレッシュレートは、DirectX 経由で設定可能です。 ただし制約があります。

    まず、モニタがサポートしているリフレッシュレート以外に設定することはできません。 そして、リフレッシュレートの変更は、フルスクリーンモード時のみ利用可能です。 Window モードでも実行したいゲームでは、リフレッシュレート変更はそもそも不可能となります。 いずれも、ドライバのサポート状況次第で、 意図したとおりの動作をしてくれないことがあります。
まとめると、モニターのリフレッシュレートは環境によって異なっていて、 かつそれを変更する絶対確実な手段は存在しない、ということです。

vsync API の問題

  • vsync はフルスクリーンモード時のみ可能

  • DirectX には vsync を取る API が提供されています。 例えば DirectDrawSurface の Flip メソッド等には、 vsync の有無を引数で指定可能です。 ただしこれはフルスクリーンモード時のみ有効です。

  • ビデオカードのベンダごとの実装に左右される

  • 前述の Flip メソッド等は、 ビデオカードのベンダごとの実装に左右されやすく、 全く機能しない場合もあります。

vsync の代替手段

モニタのリフレッシュレートを目的の値に設定できないか、 vsync API が利用できない場合、代替手段が必要です。 一般的に、高精度のタイマーを利用します。 vsync を行う場合にくらべ、 ゲーム画面の表示がガクガクと不安定になってしまいますが、 背に腹は変えられません。 いくつかの方法があります。
  • マルチメディアタイマーを利用する方法

  • Windows には、 マルチメディアタイマー という高精度タイマーが用意されています。 これは、timeGetTime 関数で取得でき、 分解能は最大 1ms 単位です。 60fps = 16.7ms なので、おおむね十分な精度です。

    マルチメディアタイマーを使用する場合、注意点があります。 マルチメディアタイマーの分解能は、 timeBeginPeriod 関数で指定した値になります。 よって、より高い精度を得たいならば、 事前に以下のように実行しておく必要があります。
    	TIMECAPS	tc ;
    	timeGetDevCaps( &tc , sizeof(TIMECAPS) );
    
    	/* マルチメディアタイマーのサービス精度を最大に */
    	timeBeginPeriod( tc.wPeriodMin );
    
    「Windows API バイブル 3」のマルチメディアタイマーの項目(p.902)には、 「timeGetTime 関数の精度は timeBeginPeriod 関数に依存しない」とありますが、 この記述は誤りの様です。 MSDN online 2001-4月リリースには、
    Windows NT:timeGetTime 関数の既定の精度は、 マシンによっては 5 ミリ秒以上になる場合があります。 timeGetTime 関数の精度は、 timeBeginPeriod 関数と timeEndPeriod 関数を使って上げることができます。 こうすると、 2 つの連続した timeGetTime 関数の戻り値の差の最小値は、 timeBeginPeriod 関数と timeEndPeriod 関数を使って設定された間隔の最小値と同じになります。
    とあり、やはり timeBeginPeriod 関数に依存するようです。 (情報提供は やねうらお さんです。ありがとうございます。) 私の Win2000 環境で調べた結果でも、 やはり timeBeginPeriod 関数に依存していました。 timeGetTime 関数で、実際に 1ms 単位の精度が得られることも確認しました。

  • パフォーマンスカウンターを利用する方法

  • Windows には、 パフォーマンスカウンターという高精度カウンタがあります。 こちらは 1ms 以下の精度が確保可能です。

    ただし、パフォーマンスカウンターは、 全ての Windows 環境で利用できるわけではありません (一見利用可能に見えて、実際には常にタイマー値に 0 を返してきたり、様々です)。 また、パフォーマンスカウンターの精度は、 沢山のプロセスを実行していると下がっていきます。
より良い vsync の代替手段を確保したいなら、 マルチメディアタイマーとパフォーマンスカウンターの精度を実行時に比較して、 良いほうを選択するという手法が必要になります。

その他の困った問題

  • OS の介入でフレームレートが乱れる

  • ゲームのアプリケーションが動作している最中も、 Windows は裏で OS の様々な他のサービスをこなしています。 そのため、ゲームの安定動作が阻害されることがあります。

    DirectX の協調モードを「排他モード」に変更することで、 Windows が裏で行っているサービスの影響を最小限にできるとされています。 しかし、これは完全ではなくて、 メモリスワップの HDD アクセスが発生するときなどは、 ゲームの進行はやはり一時停止します。

    この問題を解消する方法を、私は知りません。 そもそもゲーム用途に特化した OS ではないなので、 ここら辺りが限界のようです。

  • パレット設定 API の抱える問題

  • DirectX の 256 色モード固有の問題です。 ビデオカードベンダの実装によっては、 パレット設定を行うとき、 API 内で勝手に vsync が実行されるようです。

    この理由から、256 色モードの利用は諦めるしかありません。 256 色モード自体レガシー化しているので、 さほど問題になることは無いと思いますが。 (拙作の 超連射68K では大問題になってしまった)

もう一つの vsync 問題解決方法 「t 可変」

ここまでで挙げてきた手法は、 ゲームプログラム側で意図したレートで vsync を行うことを前提としていました。 これとは逆に、モニタのリフレッシュレートは可変であるという前提に立って、 ゲームの中の時間進行自体を、 モニタのリフレッシュレートに合わせるという手法があります。

ゲーム内の 1 フレーム分の時間は、 一般にt(デルタ ティー)と言います。 前者の、 ゲーム内の 1 フレーム分の時間は一定とするスタイルを「t 固定」、 可変とするスタイルを「t 可変」、 と言います。

t = モニタのリフレッシュレートとする「t 可変」スタイルを採用することで、 一連の問題は解決することになります。 ただし実装の難易度は上昇しますし、 関連する様々な問題が発生してしまうので、注意が必要です。

[戻る]