MFC
画像をちらつかせずに描画する


HomeProgramming TipsMFC Tips[MFC-015]

クライアント領域に画像を描画するとき、やたらとちらついたりしていませんか。
ゲームのように頻繁にクライアント領域を書き換えるアプリでは、「ちらつき」はもはや「」です(笑)。

まず、なぜちらつくか考えてみましょう。
クライアント領域を書き換える場合、通常は以下の手順で処理が実行されます。
  1. WM_ERASEBKGND で背景色でクライアント領域が消去される。
  2. WM_PAINT でクライアント領域に必要な情報を表示する。
消して書くのではちらつくに決まっていますのでこれを無視させます。
WM_ERASEBKGND メッセージをフックして処理を無かったことにします。


// ------------------------------------------------------------------
// 背景を描画しない
// ------------------------------------------------------------------
BOOL CFooWnd::OnEraseBkgnd(CDC* pDC)
{
    return TRUE;
}


では、これだけで「ちらつき」が止まるかと言えば答えはノーです。
WM_PAINT で画面を書き換えるときに結局自分でクライアント領域を消すことになります。
消さなければ前の表示情報の上に新しい情報が書き足されてバギーな画面が出来ます。
でも、消すとちらつくんですよね。

これを解決するには、クライアント領域と同じサイズの裏画面を作成しておき、
そこにいろいろと表示イメージを作ってから、一気にクライアント領域に画像を転送すれば OK です。


// ---------------------------------------------------------------------
// 再描画処理
// ---------------------------------------------------------------------
void CFooWnd::OnPaint()
{
    // クライアント領域のサイズを取得する
    CRect rc;
    GetClientRect(&rc);

    // DC を取得する
    CPaintDC dc(this);

    // 裏画面用の DC を作成する
    CDC dcMem;
    dcMem.CreateCompatibleDC(&dc);

    // 裏画面作成用のビットマップを用意する
    CBitmap bmp;
    bmp.CreateCompatibleBitmap(&dc, rc.Width(), rc.Height());
    CBitmap* pOldBmp = dcMem.SelectObject(&bmp);

    // 〜 dcMem に対して各種描画処理を行う 〜
    // 下記のコードは例として書きました
    dcMem.FillSolidRect(&rc, RGB(0,0,0));
    for (int i = 0; i < 100; i++) {
        CPen pen(PS_SOLID, 5, RGB(rand() & 0xFF, rand() & 0xFF, rand() & 0xFF));
        CPen* pOldPen = dcMem.SelectObject(&pen);
        dcMem.MoveTo(rand() % rc.Width(), rand() % rc.Height());
        dcMem.LineTo(rand() % rc.Width(), rand() % rc.Height());
        dcMem.SelectObject(pOldPen);
    }

    // 裏画面(dcMem)から表画面(dc)に一気に画像を転送する
    dc.BitBlt(0, 0, rc.Width(), rc.Height(), &dcMem, 0, 0, SRCCOPY);
    dcMem.SelectObject(pOldBmp);
}


例え、一般のアプリであっても描画がちらつくだけで安っぽく見えるものです。
なるべくなら裏画面に描画して転送するような処理をするのが望まれます。

なお、この処理を行うと「ちらつき」は直りますが、新たに「たらつき」という問題が発生します。
これは転送中にちょうどモニタの描画タイミングが重なり、
上に新しい画面、下に古い画面がダブって表示される現象です。

これを防ぐには垂直ブランキング期間まで転送を待つ事になります。
しかし、OnPaint(WM_PAINT)処理内で時間待ちを行うのは行ってはいけないことです。
待っている間に次々と WM_PAINT が発生したらどうするんですかね(苦笑)。
とりあえず、フルスクリーンのゲームでも無い限りは「たらつき」は気にならないので無視しましょう。

どうしても待ちたい?その要望はフルスクリーンゲームを作りたいと解釈しますが、
そんなあなたは躊躇わず DirectX の世界へどうぞ(笑)。
但し、DirectX8 と DirectX9 では、垂直ブランキングを待つ設定方法が仕様変更されています。
…これは Tips 書かなくてもいいですよね(笑)。



 Copyright 2005 VALGUS. All Rights Reserved.