본문 바로가기

무언가 만들기 위한 지식/Win32/MFC

MFC Double Buffering(더블버퍼링) - 화면끊키지 않게하기.

예전에 임베디드를 할때, 더블버퍼링이라는 개념을 사용했었는데
MFC상에서도 Double Buffering이라는 개념이 역시 사용된다.

인터넷에서 용어상 혼돈되는 부분이 많았는데, 개인적인 생각과 약간의 추측.
그리고 결정적으로 해결하는 방법을 제시한다.
(고로 해결책과 코드는 있는데, 과정이 100% 이해가지 않았다는 것이다 ㅠ.ㅜ;)

일단 기본적으로 BMP를 출력할때 사용하게 되는 mDC(Memory Device Context)를 사용할 때도,
어느 글에서는 더블버퍼링을 사용한다고 한다.

더블버퍼링이란 두개의 영역에 번갈아가면서 그려주고 뿌려주는 역할을 하는데, 어떤 면에서는 맞고 엄밀히 말하면 다르다고 볼 수 있다.(필자생각)

BMP의 정보들을 화면에 그냥 뿌려주면 무수한 Pixel들이 출력되면서 시간차로 끊키면서 (0,0)에서 끝까지 서서히 내려오는 현상이 보일 것이다.(고화질의 경우~) 이를 방지하고, 한번에 화면에 보여주기 위해 일정 메모리 공간에 그려두었다가 한번에 뿌려주기 위해서 메모리 DC를 생성하고 메모리비트맵을 BitBlt나 StretchBlt로 뿌려주어야 한다.

BMP를 뿌려주기 위해서는 기본적으로 mDC(Memory Device Context)를 사용하고,
이외에 추가적으로 끊키는 현상이 발생한다면 mDC도 두개를 생성하며 번갈아가며 뿌려줘야지 원활하게 보일 수 있다.

가장 큰 예가 한 [600,600] 정도의 BitMap을 OnMouseMove를 통해서 계속 마우스 위치를 따라 뿌려주게되면 화면이 끊키면서 제대로 보여지지 않게 된다. 이런 상황에서는 Double Buffering을 사용하면 해결이 가능하다.

인터넷을 검색해보면 이런저런 해결책들이 있는데,
필자가 인터넷에서 발견한 것은 Header(.h)파일을 Include하고 클래스를 이용해서 DC를 약간만 변화해주면 된다.

딱히 짱구를 굴려가며 코드를 작성할 필요 없이 받은 DC를 이용하여 코드 한줄이면 코딩이 가능해서 시간이 무지하게 단축된다.

자 그럼 바로 사용법을 알아보자.


1. 가장 먼저 할일은 위의 파일을 받아 헤더파일에 추가한다.

2. 보통 화면에 뿌려주는 일은 View창에서 한다고 가정하자.
    그러면 View Class에서 메시지를 하나 추가한다.
    WM_ERASEBKGND Message를 추가하면


BOOL CtestView::OnEraseBkgnd(CDC* pDC)
{
//	return CView::OnEraseBkgnd(pDC);
	return false;
}

위 이벤트는 CWnd 객체가 배경을 지워야할때 발생한다.
return을 false로 하게되면 Invalidate를 호출했을때, 배경을 강제로 지우지 않게된다.
이 이벤트는 유효화 영역을 설정될때 호출된다고 한다.
(즉 배경을 지울때 호출, ex-Invalidate 호출후 Paint전에 배경을 지우고 새로그릴때)
한마디로, 이것은 false로 수정하면 Invalidate()를 사용할 경우, 강제적으로 Invalidate(0)을 쓰는 효과가 있다.
실제로 위 코드를 수정하지 않고 Invalidate를 호출할때 Invalidate(false)로 호출해도 같은 결과가 나온다.


false로 하여 배경을 지우지 않는 이유는 Header File에서 그와 같은 작업을 하기 때문이다.
(엄밀히 말하면 두개의 버퍼를 사용하므로.)
※ 위 설정을 통하여 Invalidate가 paint나 draw 함수를 호출하는 용도로만 사용된다고 생각해도 무방할 듯 하다.

위 이벤트에서 주석처리하고 한줄을 추가한 후에는
3. 다음과 같이 사용한다.

void CtestView::OnMouseMove(UINT nFlags, CPoint point)
{
	CClientDC pDC1(this);
	CMemDC pDC(&pDC1);
	HANDLE bmp;
	CBitmap cBitmap;
	BITMAP bmpinfo;
	CDC memdc;
	bmp = LoadImage(NULL, "v.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
	cBitmap.Attach( bmp ); 
	cBitmap.GetBitmap( &bmpinfo ); 
	memdc.CreateCompatibleDC(&pDC);
	memdc.SelectObject(bmp);
	pDC.BitBlt(point.x,point.y,500,500, &memdc, 0 ,0, SRCCOPY);
	memdc.DeleteDC();
	cBitmap.DeleteObject();
	CView::OnMouseMove(nFlags, point);
}

OnMouseMove에서 테스트를 해본 부분이다.
CClientDC pDC1(this);
CMemDC pDC(&pDC1);

여기에서 보듯이 현재 DC를 얻어온후,
추가한 헤더파일에 정의되어 있는 CMemDC Class를 생성하고, 생성시 얻어온 DC를 인자로 넘겨주면 된다.
(주의해야할 점은 얻어온 인자 pDC가 CDC * 가 아니라 CDC이므로 "->" 대신 "." 을 사용하고, 그냥 인자를 넘기는 것이 아니라 "&"를 이용해서 넘겨주어야 한다.)
※ OnDraw에서 CDC *를 받을 경우 적당히 수정해야함.

위와 같이 CMemDC를 생성하는 부분을 한줄만 추가한다면 자유롭게 해당 DC로 사용이 가능해 진다.

지식인을 보니 위 헤더파일에 대한 어느정도 설명이 있어서 추가해 본다.

http://kin.naver.com/detail/detail.php?d1id=1&dir_id=10104&docid=9357556&qb=cmVsZXZlbnQ=&enc=utf8&pid=fpGvusoi5UNssZ/3dK4sss--437289&sid=Sp4DCC7hnUoAAE-3NPY

위 글을 읽어보고, 기본적인 더블버퍼링 코드를 보면 좀 이해가 잘 간다. (필자의 경우 ^_^)

위글보고 생각해보니
CMemDC내에서는 단지 pDC의 IsPrinting()를 이용해서 사용되고 있는지 판단하고 있을 뿐이지, 나머지는 비슷하다.

결론은 위 지식인 내용을 참고해서 직접 더블버퍼링을 구해서 사용도 가능하지만,
그냥 위 Header File을 Include해서 간단하게 사용하는 방법이 있다.
(필자는 당분간 위 헤더파일로 사용해 볼 예정이다.! - 쓰다보면 메모리낭비등 단점이 발견되겠지 ^_^)


※ OnMouseMove가 아닌 OnDraw등에서 그려줘도 결과는 같다.