e-fs.info

ダブルバッファリング


ちらつきがなぜ起きる?


画面を更新をする時に「計算→画像描画→画面に描画」という一連の動作をしています。
一連の動作をしている時に逐一更新されているので、画像が「あるとき」や「ないとき」に画面更新されるのでちらつくように見える。

対処の方法


ではどうすればいいか?
ちらつきを防止する一般的な方法は、画面を仮想的にもうひとつ作り(それを裏画面(バックサーフェイス)と一般的に言ってます)それに計算の結果を描画し、描画結果を反映させるというものです。
これをダブルバッファリングといいます。
計算しながら描画するものと違い計算しきった後描画するのでちらつかないです。
そのプログラムを次項に示します。

そして、実行サンプル!

どうすれば?


HDC Back_Init_Surface(HWND hwnd)
{
    HDC hdc,BackDC;
    HBITMAP hBackBMP;

    hdc = GetDC(hwnd);
    BackDC = CreateCompatibleDC(hdc);
    hBackBMP = CreateCompatibleBitmap(hdc,WINX,WINY); // WINX,WINYは画面の大きさ
    SelectObject(BackDC,hBackBMP);        // 作ったものを絡める
    PatBlt(BackDC,0,0,WINX,WINY,WHITENESS);    // 白で塗りつぶす
    DeleteObject(hBackBMP);                                    // 借りたものは返さないと。
    Release(hwnd,hdc);            // 借りたものは返す。

    return BackDC;
}
上のソースをウィンドウを作成時に関数として入れて戻り地のHDCに描画すればちらつきはなくなるはずです。
具体的に
    HDC BackDC = Back_Init_Surface(hwnd);//BackDCは裏画面
    for(;;){
        TextOut(BackDC,0,0,"Flowstyle",9);
        BitBlt(hdc,0,0,WINX,WINY,BackDC,0,0,SRCCOPY);//hdcは画面本体
    }

詳しくはフレームワーク内で使ってますので解析でもすれば・・・無理?

実際のプログラム

#include <windows.h>
#include <windowsx.h>

const int WINX = 320;
const int WINY = 240;
const char* TITLE = "〜うぃんどう〜";

LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);

HDC Back_Init_Surface(HWND hwnd)
{
    HDC hdc,BackDC;
    HBITMAP hBackBMP;

    hdc = GetDC(hwnd);                       // 今の画面を取得
    BackDC = CreateCompatibleDC(hdc);        // hdcに似た領域
    hBackBMP = CreateCompatibleBitmap(hdc,WINX,WINY); // WINX,WINYは画面の大きさ
    SelectObject(BackDC,hBackBMP);        // 作ったものを絡める
    PatBlt(BackDC,0,0,WINX,WINY,WHITENESS);    // 白で塗りつぶす
    DeleteObject(hBackBMP);                                               // 借りたものは返さないと。
    ReleaseDC(hwnd,hdc);                     // 借りたものは返す。

    return BackDC;//裏画面を返す
}

int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE,LPSTR lpCmdLine,int nCmdShow)
{
    MSG msg;
    WNDCLASSEX wc;
    HWND hwnd;

// --- 設定 Start --- //
    ZeroMemory(&wc,sizeof(WNDCLASSEX));    // 内部の情報を全て0に!
// --- 情報の登録 --- //
    wc.cbSize        = sizeof(WNDCLASSEX);
    wc.style        = CS_HREDRAW|CS_VREDRAW;
    wc.lpfnWndProc    = WndProc;
    wc.lpszMenuName = NULL;
    wc.hInstance    = hInstance;
    wc.hIcon        = (HICON)LoadImage(NULL,MAKEINTRESOURCE(IDI_APPLICATION),IMAGE_ICON,0,0,LR_SHARED);
    wc.hIconSm        = (HICON)LoadImage(NULL,MAKEINTRESOURCE(IDI_APPLICATION),IMAGE_ICON,0,0,LR_SHARED);
    wc.hCursor        = (HCURSOR)LoadImage(NULL,MAKEINTRESOURCE(IDC_ARROW),IMAGE_CURSOR,0,0,LR_DEFAULTCOLOR | LR_SHARED);
    wc.hbrBackground= (HBRUSH)GetStockObject(WHITE_BRUSH);
    wc.lpszClassName= TITLE;
// --- 情報の登録 End --- //

    if(!RegisterClassEx(&wc))return FALSE;    // ちゃんと登録されてる?
// --- 設定 End --- //

// --- ウィンドウを表示のために作ります --- //
    DWORD dwStyle = WS_OVERLAPPEDWINDOW & ~WS_THICKFRAME & ~WS_MAXIMIZEBOX; // ウィンドウの大きさ変更できないように

    hwnd = CreateWindowEx(0,TITLE,TITLE,dwStyle, // タイトル、スタイルの登録
        GetSystemMetrics(SM_CXSCREEN)/2 - WINX/2,// スクリーンの大きさを取得して真ん中に表示
        GetSystemMetrics(SM_CYSCREEN)/2 - WINY/2,// 上に同じ Y座標版
        CW_USEDEFAULT,    // 今のところ大きさはどうでもいいよ
        CW_USEDEFAULT,    // 上に同じ。
        NULL,NULL,hInstance,NULL);

    if(!hwnd)return FALSE;    // ちゃんと作れた?
// --- ウィンドウ表示のために作ります End --- //

// --- ウィンドウを WINX WINYの大きさに --- //
    RECT window_rect;
    SetRect(&window_rect,0,0,WINX,WINY);
    AdjustWindowRectEx(&window_rect,GetWindowLong(hwnd,GWL_STYLE),GetMenu(hwnd) != NULL,GetWindowLong(hwnd,GWL_EXSTYLE));
    const int nWidth  = window_rect.right  - window_rect.left;
    const int nHeight = window_rect.bottom - window_rect.top;
    SetWindowPos(hwnd,NULL,0,0,nWidth,nHeight,SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
// --- ウィンドウを WINX WINYの大きさに END --- //

    ShowWindow(hwnd,nCmdShow);    // ウィンドウ表示するよ
    UpdateWindow(hwnd);

    for(;;){    // メインループ
        if(PeekMessage(&msg,NULL,0,0,PM_NOREMOVE)){
            if(!GetMessage(&msg,(HWND)NULL,0,0))break;    // メッセージが着たら処理するよ
            TranslateMessage(&msg);        // キーが押されたかどうか見てます。見てます。
            DispatchMessage(&msg);        // ウィンドウにメッセージ送信
        }else{
            // 毎回の処理
        }
        Sleep(1);    // ちょっと息抜き
    }

    return (int)msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam){
    PAINTSTRUCT ps;
    static int x=0,y=0;            // 自分がどこにいるかの保持
    static HDC backhdc;            // 裏画面

    switch(msg){
        case WM_KEYDOWN:    // 何かキーを押された?
            if(wParam != VK_ESCAPE){return 0;}// Esc押される以外は何もしないよ
        case WM_DESTROY:    // 終了するよ。
            KillTimer(hwnd,1); // Timer片付け(Timer廃棄)
            ReleaseDC(hwnd,backhdc);    // 裏画面を片付け
            PostQuitMessage(0);
            return 0;
        case WM_PAINT:{                            // Paintのメッセージ
            char str[] = "●";
            TextOut(backhdc,x,y,str,lstrlen(str));    // 座標(x,y)の位置にstrを書きます。

            HDC hdc = BeginPaint(hwnd,&ps);            // 描く準備
            BitBlt(hdc,0,0,WINX,WINY,backhdc,0,0,SRCCOPY);    // 今までbackhdcに描いてきたものを表画面に転送。
            PatBlt(backhdc,0,0,WINX,WINY,WHITENESS);        // 裏画面を白で初期化
            EndPaint(hwnd,&ps);                    // 準備物を片付け
            return 0;
        }
        case WM_TIMER:                            // 一定時間ごとに動くよ
            x++;                                // とりあえず右方向に動く
            InvalidateRect(hwnd,NULL,FALSE);    // 動いたら描画し直せ!
            return 0;
        case WM_CREATE:                                // Windowを作ったときに一度だけ実行します。
            SetTimer(hwnd,1,100,NULL);                // Timerを使いますよ(Timer準備物)
            backhdc = Back_Init_Surface(hwnd);        // 裏画面を生成
            return 0;
        default:
            break;
    }
    return (DefWindowProc(hwnd,msg,wParam,lParam));
}
コメントが増えてたりするのは気のせい。