描画の「ちらつき」を抑える

これまでにも描画の「ちらつき」を抑えて描画するプログラムは幾つか紹介して来ました。
ここでは「ちらつき」を抑える方法を、要点をまとめてステップアップで説明します。

前田稔(Maeda Minoru)の超初心者のプログラム入門

画像のチラツキを無くす

  1. Step1
    再描画する範囲を矩形で設定します。
    矩形で設定した範囲が多少「チラツク」のは避けられません。
        RECT        Box= { 0,0,200,64 };
        InvalidateRect(hWnd,&Box,TRUE);
        
  2. Step2
    InvalidateRect() を FALSE に設定すると大分チラツキを抑えられます。
    InvalidateRect(g_hWnd,NULL,TRUE);
    InvalidateRect(g_hWnd,NULL,FALSE);
    但し、現在の画面の上から描画されるので、元の画像が残ることがあります。
    元の画像が残るときの対策です。
    この方法は、背景画像の上から描画する時や透明色を使っている画像には使えません。
    画面をクリアするソースコードです。但しクリアするとチラツキが増します。
        RECT        RC= { 0, 0, WIDTH, HEIGHT };
        FillRect(hBackDC,&RC,(HBRUSH)GetStockObject(BLACK_BRUSH));
        
  3. Step3
    WM_PAINT: から呼び出すとオーバーヘッドが生じるので WM_TIMER: などから直接呼び出します。
    WM_PAINT: 以外から描画する時は、HDC は hWnd から GetDC() で取得します。
        HDC     hDC;
            hDC= GetDC(hWnd);
            //描画を行う
            ReleaseDC(hWnd,hDC);
        
  4. Step4
    BackBuffer を生成して、画面全体のイメージを作成してから一挙に FrontBuffer に転送します。
    これで画面を切り替える時間を最小限に抑えることが出来ます。
    それでも WM_PAINT: から呼び出すとちらつくので WM_TIMER: などから直接呼び出します。
    私の経験では、これでチラツキが完全に無くなりました。
    1. BackBuffer の定義です。
              HDC         hBackDC=NULL;       //Back Buffer DC
              HBITMAP     hBackBitmap=NULL;   //Back Buffer HBitMap
              
    2. BackBuffer を生成する関数です。
      表画面と共通の設定で、640*480 の BackBuffer を生成しています。
              void  CreateBuf(HWND hWnd)
              {   HDC     hDC;
                  hDC= GetDC(hWnd);
                  hBackDC= CreateCompatibleDC(hDC);
                  hBackBitmap= CreateCompatibleBitmap(hDC,640,480);   //表画面と同じ画面生成
                  SelectObject(hBackDC,hBackBitmap);                  //DC と画面本体を関連付ける
                  ReleaseDC(hWnd,hDC);
              }
              
    3. BackBuffer を使って描画をする関数です。
              VOID  Render()
              {   HDC     hDC;
                  RECT    rc;
                  int     wl,hl;
      
                  GetClientRect(g_hWnd,&rc);
                  //裏画面を黒でクリア
                  FillRect(hBackDC,&rc,(HBRUSH)GetStockObject(BLACK_BRUSH));
                  //描画の実行
                  ☆描画は hBackDC に対して行います。
                  //裏画面から表画面に一括して転送
                  wl= rc.right-rc.left;
                  hl= rc.bottom-rc.top;
                  hDC= GetDC(g_hWnd);
                  BitBlt(hDC,0,0,wl,hl,hBackDC,0,0,SRCCOPY);
                  ReleaseDC(g_hWnd,hDC);
              }
              
    4. BackBuffer の開放です。
              case WM_DESTROY:
                  DeleteDC(hBackDC);
                  DeleteObject(hBackBitmap);
                  PostQuitMessage(0);
                  return 0L;
              
  5. Step5
    BackBuffer を使って描画する関数をメッセージループから直接呼び出します。
    メッセージがポストされない時に呼び出されるので、DirectX と同様にスムースに描画されます。
    1. メッセージループから直接呼び出すときはタイマ割り込みが使えません。
      タイマ割り込みに代えて timeGetTime() で時刻を取得します。
      timeGetTime() 使うときは、mmsystem.h を取り込んで winmm.lib をリンクします。
              #include    <windows.h>
              #include    <mmsystem.h>
              #pragma     comment(lib,"winmm.lib")
              #define     SAFE_DELETE(p)  { if (p) { delete (p); (p)=NULL; } }
              #define     SAFE_DELDC(p)   { if (p) { DeleteDC (p);   (p)=NULL; } }
              #define     SAFE_DELOBJ(p)  { if (p) { DeleteObject(p); (p)=NULL; } }
      
              HDC         hBackDC=NULL;       //Back Buffer DC
              HBITMAP     hBackBitmap=NULL;   //Back Buffer HBitMap
              
    2. CALLBACK 関数の WM_CREATE: です。
      CreateBuf() で Back Buffer を作成します。
              case WM_CREATE:
                  CreateBuf(hWnd);
                  break;
              
    3. メッセージループから Render() 関数を呼び出します。
              int PASCAL  WinMain(HINSTANCE hInst, HINSTANCE, LPSTR,int nCmdShow)
              {   MSG         msg;
                        :
                  ZeroMemory(&msg,sizeof(msg));
                  while(msg.message!=WM_QUIT)
                  {   if (PeekMessage(&msg,NULL,0U,0U,PM_REMOVE))
                      {   TranslateMessage(&msg);
                          DispatchMessage(&msg);
                      }
                      else    Render();
                  }
                  return msg.wParam;
              }
              
    4. BackBuffer を使って描画をする関数です。
      timeGetTime() でミリ秒単位の時刻を取得します。
              VOID  Render()
              {   HDC     hDC;
                  RECT    rc;
                  int     nowTime,wk;
                  static  int  tim; 
      
                  nowTime= timeGetTime();
                  wk= (nowTime/50)%360;       //ミリ秒単位の時刻を元に計算
                  if (wk==tim)    return;     //前回と同じ値のときは描画の必要が無い
                  tim= wk;
                  GetClientRect(g_hWnd,&rc);
                  //裏画面を黒でクリア
                  FillRect(hBackDC,&rc,(HBRUSH)GetStockObject(BLACK_BRUSH));
                  //描画の実行
                  ☆描画は hBackDC に対して行います。
                  //裏画面から表画面に一括して転送
                  hDC= GetDC(g_hWnd);
                  BitBlt(hDC,0,0,rc.right-rc.left,rc.bottom-rc.top,hBackDC,0,0,SRCCOPY);
                  ReleaseDC(g_hWnd,hDC);
              }
              
    5. WM_DESTROY: ではリソースを開放して下さい。
              case WM_DESTROY:
                  SAFE_DELDC(hBackDC);
                  SAFE_DELOBJ(hBackBitmap);
                  PostQuitMessage(0);
                  return 0L;
              

超初心者のプログラム入門(Win32API C++)