본문 바로가기

공부/MFC

MFC 더블 버퍼링 [ Double Buffering ]

책의 608 페이지 에 있는 내용으로

프로젝트명은 RedrawDemo로  작성한다.
 

윈도우의 깜박임을 제거하는 최선의 방법은 더블 버퍼링 이다.

더블 버퍼링은 화면 DC에 직접 출력하는 것이 아닌 화면 DC와 호환이 되는 메모리 DC를 만들어서 모든 그리기 작업이 끝난 값을 집어넣어둔 후 메모리 DC의  내용을 화면 DC로 복사하는 기법이다.

 이렇게 하면 화면의 크기를 변경하더라도 흰색 깜박임이 발생하지 않게 된다.[흰색으로 초기화 시키는게 아닌 메모리 DC의 내용을 가져오기만 하기 때문에.]

전에 쓴 OnPaint()함수의 코드에서 CPaintDC dc(this); 를 CBufferDC dc(this); 로 변경하고 CBufferDC 클래스를 프로젝트에 인클루드 하면 간단히 더블 버퍼링을 구현할 수 있다. 

라는데 솔까 소스 보니까 개뻥이다.

왜냐하면 해당 클래스를 만들어야하고

헤더파일과 cpp파일까지 만들어서 집어넣어줘야 컴파일이 되기 때문.[안그러면 에러남]

아래는 BufferDC.h의 내용이다.

class CBufferDC : public CDC  
{

private:
CBufferDC() { }
CBufferDC(const CBufferDC &src) { }
CBufferDC& operator=(const CBufferDC &src) { }

protected:
BOOL Attach(HDC hDC);
HDC Detach();

private:
CWnd* m_pParent; //대상 윈도우에 대한 포인터
CDC* m_pTarget; //대상 윈도우 DC에 대한 포인터
PAINTSTRUCT m_PaintStruct;
CRect m_RcClient, m_RcWindow; //대상 윈도우의 크기 정보

CDC m_MemoryDC; //버퍼 DC
CBitmap m_MemoryBmp, *m_pOldMemoryBmp; //버퍼링을 위한 비트맵


여기에 주석이 안달린 곳.

PAINTSTRUCT 라는 구조체는 화면을 그리기 위한 정보를 담는 구조체로
Win32 API 프로그래밍 방식에서 볼 수 있다.

MFC에서는 WM_PAINT 메시지가 전달되었을 때 OnPaint 함수를 호출하고 CPaintDC클래스 객체를 생성해서 그리기를 수행하는것이 일반적이나 

Win32 API에서는 ::BeginPaint() 함수를 호출하여 DC핸들(이게 바로 HDC)을 얻고 이 핸들로 화면에 그리기 작업을 수행한다. DC핸들을 사용한 후에는 ::EndPaint()함수를 호출해서 핸들을 반환한다.

MFC에서는 객체가 소멸되는 시점에 소멸자가 호출되어 이런 처리를 한다.


m_MemoryBmp와 m_pOldMemoryBmp 멤버는 CBufferDc 클래스 객체를 생성할 때 내부적으로 비트맵을 동적으로 생성하여 이를 선택[SelectObject()] 하게 된다.
따라서 CBufferDc클래스 객체에 그리기를 수행했다면 그것은 선택한 비트맵에 그려지게 된다.


다음은 BufferDC.cpp의 내용 중 일부이다

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
CBufferDC::CBufferDC(CWnd *pParent)
    : m_pParent(pParent)
{
ASSERT(pParent);

//여기부터
m_pTarget = m_pParent->BeginPaint(&m_PaintStruct);
m_pParent->GetClientRect(&m_RcClient);
m_pParent->GetWindowRect(&m_RcWindow);            //대상 윈도우에 대한 정보를 수집한다.


m_MemoryDC.CreateCompatibleDC(m_pTarget); //대상 윈도우에 대한 DC를 생성한다.

       //여기부터
m_MemoryBmp.CreateBitmap(m_RcClient.Width(), m_RcClient.Height(), 
m_MemoryDC.GetDeviceCaps(PLANES),
m_MemoryDC.GetDeviceCaps(BITSPIXEL), 0);
m_pOldMemoryBmp = m_MemoryDC.SelectObject(&m_MemoryBmp);
//대상 DC에 대한 메모리 비트맵을 생성하여 Select 한다.
 


Attach(m_MemoryDC); //메모리 버퍼에 Attach한다.
}


여기에서 핵심적으로 사용된 코드인 CreateBitmap 함수의 원형은 다음과 같다.


  BOOL CreateBitmap(
int nWidth,
int nHeight,         //뻔한 너비 높이.
UINT nPlanes,      //비트맵의 색상 플래인[면]
UINT nBitcount,      //한 픽셀을 표시할 때 사용되는 비트 수        두 값 모두 CDC클래스의 GetDeviceCaps()메서드를 이용해서 알아내면 됨
const void* lpBits// 이 인자는 생성할 비트맵이 저장된 주소이며 이 값이 NULL이면 초기화되지 않은 상태로 비트맵이 만들어짐 = 검은색 비트맵
);


다음으로는

생성자보다 중요하다는 소멸자 코드.




CBufferDC::~CBufferDC()
{

m_pTarget->BitBlt(
m_PaintStruct.rcPaint.left,
m_PaintStruct.rcPaint.top, 
m_PaintStruct.rcPaint.right - m_PaintStruct.rcPaint.left, 
m_PaintStruct.rcPaint.bottom - m_PaintStruct.rcPaint.top, 
&m_MemoryDC,
m_PaintStruct.rcPaint.left,
m_PaintStruct.rcPaint.top, SRCCOPY);
//메모리 DC의 내용을 대상 윈도우에 출력한다.
//내부적으로 비트맵에 출력한 것이므로 해당 비트맵을 1:1로 복사한다.


m_MemoryDC.SelectObject(m_pOldMemoryBmp);
m_pParent->EndPaint(&m_PaintStruct);

Detach();
}

소멸자의 코드를 보면 CBitmap 클래스 객체를 선택한 메모리 DC의 내용을 연결된 화면 DC에 SRCCOPY 모드로 출력한 후 자원을 반납하고 처리를 완료한다.

화면 DC에 직접 그리는 것도 좋지만 중간 단계를 거쳐서 출력하면 출력 전 과정[흰색으로 지우는]이 화면에 나타나지 않으므로 깜박임이 없어지게 된다.

WMERASEBKGND 메시지 핸들러 함수를 등록하여 아무런 처리도 하지 않도록 수정하고 OnPaint()함수에서 CBufferDC 클래스를 활용하여 더블 버퍼링을 구현한다면 깜빡임을 완벽히 해결할 수 있다. 



스샷 올려봐야 의민 없지만.. 어차피 윈도우 사이즈 변경하면서 깜빡임의 유무를 보는거인지라.;

만드는 방법은 전의 글에서 ReDrawDemo에 

Buffer.cpp와 .h를 만들어서 속에 코드까지 다 입력을 해야한다[책에선 무책임하게도 코드를 알려주기보다 그냥 인터넷에서 찾으라고 한다.]

고로 그냥 책의 소스를 그대로 보고 이해하는게 더 빠를것이다.






이건 코드파일