. 더블 버퍼링

몇 가지 최적화에 의해 실행속도는 대폭적으로 향상이 되었지만 아직 이 프로그램은 큰 문제점을 하나 가지고 있다. 글자를 삽입, 삭제할 때마다 화면 전체를 다시 그리기 때문에 화면 깜박임이 무척 심하다는 점이다. 컴퓨터가 웬만큼 빠르면 이 깜박임이 눈에 잘 안 보일 수도 있는데 최대화 시킨 상태에서 한 페이지 가득 글자를 채워보고 문장을 입력해보면 깜박임을 느낄 수 있을 것이다.

만약 그래도 깜박임을 느끼기 어렵다면 마우스를 드래그하여 선택을 해보아라. 선택 상태가 바뀔 때도 화면을 다시 그리는데 이때는 컴퓨터가 아무리 빨라도 선택영역이 깜박거리는 것을 확인할 수 있다. 느린 컴퓨터에서는 거의 못 봐줄 정도의 수준이며 이런 깜박거림은 눈을 피곤하게 만들고 프로그램의 질을 현격하게 떨어뜨린다.

그렇다면 깜박임이 발생하는 이유는 뭘까? 문서 내용이 바뀌면 변경된 부분을 지우고 다시 그려야 하는데 이때 변경된 부분을 지웠을 때의 흰 바탕이 다시 그려질 때까지 눈에 보이기 때문에 화면이 깜박거리는 것이다. 그렇다면 문서가 변경되어도 지우지 말고 그냥 그리기만 하면 어떻게 될까? 이렇게 하기 위해 배경 브러시를 없애 보자.

 

int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance

       ,LPSTR lpszCmdParam,int nCmdShow)

{

     ....

    WndClass.hbrBackground=NULL;

 

윈도우 클래스의 배경 브러시를 NULL로 바꾸면 작업영역을 지울 브러시가 없기 때문에 WM_ERASEBKGND 메시지가 처리되지 않는다. , 배경 브러시를 지정하지 않는다는 것은 작업영역을 내가 알아서 지울 테니 너는 가만히 있어라는 명령이다. 그래서 작업영역을 무효화해도 화면을 지우지 않는다. 이렇게 한 후 실행해보자.

결과는 정말 끔직하다. 문자는 제대로 출력되지만 배경을 지우지 않으므로 문자가 없는 줄간 사이는 뒤쪽 배경이 그대로 보인다. 이 상태에서 새로운 문자를 입력하면 배경을 지우지 못하더라도 입력된 문자를 볼 수는 있다. 하지만 삭제할 때는 사라져야 할 문자가 그 자리에 그대로 남아 있게 된다. 이 실험에서 알 수 있듯이 새로 출력되는 내용이 기존 화면을 완전히 덮어버리지 않는 한 기존의 화면을 지우지 않고 새 화면을 출력할 수는 없다.

하지만 화면 깜박임을 최소화할 수 있는 여러 가지 방법들이 있다. 우선 제일 간단한 방법은 무효영역을 가급적 좁게 설정함으로써 최대한 빨리 그리도록 하는 것이다. 깜박이기는 하지만 워낙 빨리 그리기 때문에 눈에 거의 안 보이게 만드는 것이며 현실적으로 충분히 가능한 방법이다. 그러나 이 방법은 깜박임을 최소화하는 것이지 근본적으로 없애는 방법은 아니다. 문서 전체가 한꺼번에 바뀌게 되면 어쩔 수 없이 작업영역 전체가 무효화되고 따라서 깜박임이 발생한다.

깜박임을 해결할 수 있는 가장 완벽한 방법은 메모리 비트맵을 사용하는 더블 버퍼링이다. 메모리상에서 출력할 내용을 미리 그려 놓고 BitBlt으로 고속 전송함으로써 순식간에 화면을 갱신하는 방법이다. CD-DOM Doc 디렉토리에 더블 버퍼링에 대한 문서와 예제가 작성되어 있으므로 참조하기 바란다. 화면 깜박임없이 공이나 문자를 부드럽게 이동시키는 방법에 대해 설명하고 있다.

 

메모리의 비트맵에 출력하는 과정에서도 바탕색으로 지우고 그 위에 글자를 출력하는 절차가 필요하지만 이 처리는 화면상으로 보이지 않기 때문에 문제가 되지 않는다. BitBlt으로 출력할 때도 기존 출력 내용을 지우지 않고 그 위에 바로 덮어쓰기 때문에 전혀 깜박거리지 않는다. 더블 버퍼링에 사용할 비트맵을 전역변수로 선언하고 OnCreate에서 이 변수를 NULL로 초기화한다.

 

HBITMAP hBit;

 

BOOL OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct)

{

     ....

     hBit=NULL;

 

OnPaint에서는 화면으로 바로 출력하지 않고 이 비트맵에 작업영역을 먼저 그린 후 비트맵을 화면으로 전송한다. 다음과 같이 코드를 수정해보자.

 

void OnPaint(HWND hWnd)

{

    HDC hdc, hMemDC;

     PAINTSTRUCT ps;

     int l;

     RECT crt;

     int s,e;

    HBRUSH hBrush;

    HBITMAP OldBitmap;

 

     hdc=BeginPaint(hWnd,&ps);

 

     GetClientRect(hWnd,&crt);

     s=yPos/LineHeight;

     e=s+crt.bottom/LineHeight;

 

    hMemDC=CreateCompatibleDC(hdc);

    if (hBit == NULL) {

        hBit=CreateCompatibleBitmap(hdc,crt.right,crt.bottom);

    }

    OldBitmap=(HBITMAP)SelectObject(hMemDC,hBit);

 

    hBrush=CreateSolidBrush(GetSysColor(COLOR_WINDOW));

    FillRect(hMemDC,&crt,hBrush);

 

     for (l=s;l<=e;l++) {

        if (DrawLine(hMemDC,l) == 0)

               break;

     }

    BitBlt(hdc,0,0,crt.right,crt.bottom,hMemDC,0,0,SRCCOPY);

 

    DeleteObject(hBrush);

    SelectObject(hMemDC,OldBitmap);

    DeleteDC(hMemDC);

     EndPaint(hWnd,&ps);

}

 

작업영역의 크기와 동일한 크기의 비트맵을 만들고 배경색으로 채운 후 이 비트맵에 문서 내용을 먼저 출력하였다. 출력코드는 화면 DC 대신 메모리 DC를 사용한다는 것만 빼고 바뀐 것이 없다. 비트맵에 문서 내용을 다 출력한 후 화면으로 고속 전송하면 화면이 전혀 깜박거리지 않으며 감쪽같이 출력될 것이다.

출력을 마친 후 메모리 DC만 해제하고 비트맵은 그대로 둔다. 매번 그리기를 할 때마다 비트맵을 생성할 필요는 없기 때문이다. 다음 OnPaint를 처리할 때 어차피 비트맵 전 영역을 배경색으로 채운 후 문서를 출력하므로 비트맵은 계속 사용해도 상관없다. 비트맵을 다시 생성해야 할 시점은 작업영역의 크기가 바뀔 때인데 비트맵 크기는 작업영역의 크기와 같아야 하기 때문이다. OnSize에서 이 처리를 위해 비트맵을 제거한다.

 

void OnSize(HWND hWnd, UINT state, int cx, int cy)

{

     if (state != SIZE_MINIMIZED) {

          if (nWrap) {

               UpdateLineInfo();

          }

          UpdateScrollInfo();

          if (GetFocus()==hWnd) {

               SetCaret();

          }

        if (hBit) {

            DeleteObject(hBit);

            hBit=NULL;

        }

     }

}

 

OnSize에서 변경된 작업영역 크기에 맞게 비트맵을 다시 생성하는 것이 아니라 단순히 비트맵을 파괴하기만 하며 hBit NULL로 만든다. 이렇게 해놓으면 OnPaint에서 작업영역 크기에 맞게 비트맵을 다시 생성할 것이다. OnSize는 비트맵을 다시 만드는 것이 아니라 무효화시킴으로써 OnPaint가 비트맵을 재생성하도록 하기만 한다. 이렇게 사용한 비트맵은 프로그램이 종료될 때 파괴해야 하므로 OnDestroy에도 동일한 코드가 필요하다.

 

void OnDestroy(HWND hWnd)

{

     PostQuitMessage(0);

     free(buf);

     free(arHanWidth);

     free(pLine);

    if (hBit) {

        DeleteObject(hBit);

        hBit=NULL;

    }

}

 

이제 실행해보면 어떤 동작을 하더라도 화면이 깜박거리지 않는다. 메모리상의 비트맵에 미리 그린 후 일괄 전송하기 때문이다. 하지만 이 방법은 화면 전체를 비트맵으로 작성하기 때문에 화면이 커지면 느려지는 단점이 있다. 1600*1200 같은 고해상도 모니터에서 화면을 최대화해놓고 편집해보면 확실히 느리다는 것을 알 수 있다. 메모리 비트맵의 크기가 커지면 속도가 느려지기도 하지만 단 한 줄이 바뀌어도 전체 화면을 다 그려야 하므로 최적화에 다소 불리하다. 그래서 좀 다른 방법을 찾아 보아야 한다.