tanaka's Programming Memo

プログラミングについてのメモ。

C#で垂直帰線を待つ

ゲームなどの更新処理で、フレームレートを固定させたい場合、StopwatchクラスのElapsedMillisecoundsなどを使って一定時間待つような処理を入れる方法がある。この方法は実装が簡単なのだが、画面の更新と描画のタイミングが重なってしまうと、描画がずれる「ティアリング」という現象が起きてしまう。

「ティアリング」を避けるには、ディスプレイの垂直帰線期間になるまで待ち、その直後に画面を更新する方法がある。垂直帰線期間が訪れる間隔はディスプレイの「リフレッシュレート」で固定されるので、この方法でもフレームレートを固定することが出来る。ゲーム機などの同期はこちらの方式である。

実装

DirectXを描画に利用している場合は、デバイスの生成時の設定で自動的に画面の切り替えを垂直帰線期間と同期させることが出来るので特に何もしなくてもよい。

C#でGraphicsを使って描画をしている場合などは自分で垂直帰線期間をチェックする必要がある。Direct3Dデバイスを生成してGetRasterStatus()を利用することで実現できる。

DirectXDirect3Dが使えるように、参照の追加を行う。

  • 垂直帰線同期をさせたいプロジェクトを開き、【ソリューションエクスプローラ】に表示されている【参照設定】を右クリックして、【参照の追加】を選ぶ。

  • [Ctrl]キーを押しながら、「Microsoft.DirectX」と「Microsoft.DirectX.Direct3D」を選択して【OK】ボタンを押して追加する。


プロジェクトに、垂直帰線を待つ処理を持ったクラスを作成する。

  • プロジェクトを右クリックして、【追加】→【クラス】を選ぶ。
  • 作成したクラスの名前を「CCheckRaster」とする。
  • 作成したクラスに以下のソースコードを記述する。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;

/**
 * 垂直帰線期間を待つためのクラス
 * 1)参照設定に「Microsoft.DirectX」と「Microsoft.DirectX.Direct3D」を追加する。
 * 2)このファイルをプロジェクトに追加する。
 * 3)自分のプログラムに「CCheckRaster checkRaster;」として変数を宣言する。
 * 4)自分のプログラムの初期化処理の時に、「checkRaster = new CCheckRaster(this);」として初期化をする。
 * 以上で初期化終わり。
 * あとは、垂直帰線を待ちたい場所で「checkRaster.wait();」とする。
 */
class CCheckRaster
{
    // Direct3Dデバイス
    private Microsoft.DirectX.Direct3D.Device device = null;

    /**
     * 初期化。
     */
    public CCheckRaster(System.Windows.Forms.Control parent)
    {
        try
        {
            // Set up presentation parameters and create the 
            // Direct3D device.
            PresentParameters presentParams = new PresentParameters();
            presentParams.Windowed = true;
            presentParams.SwapEffect = SwapEffect.Discard;
            device = new Device(0, DeviceType.Hardware, parent, CreateFlags.SoftwareVertexProcessing, presentParams);
        }
        catch (DirectXException)
        {
            device = null;
        }
    }

    /**
     * 垂直帰線期間を待つ。
     */
    public void wait()
    {
        RasterStatus rs;
        if (device == null)
        {
            return;
        }
        try
        {
            do
            {
                rs = device.GetRasterStatus(0);
                Application.DoEvents();
            } while (!rs.InVBlank);
        }
        catch (Exception ee)
        {
            ee.ToString();
        }
    }
}

使いたいプログラムに、垂直帰線のチェッククラスを追加する。

  • 自分のプログラムを開いて、クラスにCCheckRasterクラスを定義する。
CCheckRaster checkRaster;
  • フォームを表示した直後などの処理(FormのShownイベントなど)内で、以下のように「CCheckRaster」クラスを生成する。
checkRaster = new CCheckRaster(this);

以上で、垂直帰線期間を待つ準備が完了。

あとは、バックバッファなどに描画を終えて、最後に画面の更新をする直前で

checkRaster.wait();

を呼び出すことで、垂直帰線期間になるまで処理を待ってくれる。

「checkRaster.wait();」のあとで、バックバッファをフォームに描画すれば「ティアリング」を防ぎ、ディスプレイのリフレッシュレートとの同期も出来る。