2014년 7월 11일

GDI 를 이용한 더블 버퍼링 (double buffering)


GDI 기술은 사실상 수십년도 더된 구닥다리(?) 기술로, GDI+를 넘어 최근 M$ 운영체제는 그래픽 카드의 GPU를 이용해 바탕화면에서 조차 하드웨어 가속을 위해 3D 기반의 라이브러리인 DirectX를 경량화한 Direct2D 을 제공하고 있다.

이 기술은 윈도우 비스타 이후 시리즈만 사용가능하며 Aero Glass 라는 명칭으로 광고를 하고 있다.

구닥다리 기술인 GDI 을 개선하고자 알파블랜딩과 객체지향적인 라이브러리를 구조를 가진 GDI+ 가 있지만, 이번 글에서는 GDI 를 이용한 전통적인 2D 렌더링 방법을 살펴보자.




위 이미지를 보면, 만개의 사각형을 더블버퍼링 기법을 이용하여 렌더링 하고 있으며 약 62msec 이 소요되었음을 알수있다.

이 소스의 핵심사항은 단순히 그리는 기능이 아닌 아래와 같은 추가 기능을 적용하였다.


  • 더블 버퍼링을 사용하여 화면의 깜빡거림(flickering)을 방지
  • 화면의 확대 축소 기능을 제공한다. 즉 줌 효과를 어떤식으로 적용하는지 알수있다.
  • 화면의 원점 위치를 변경한다. 이를 통해 마우스나 멀티터치 입력시 중심위치를 변경가능하다.
  • 좌상단이 0,0 인 윈도우의 기본 좌표계를 X,Y 직교 좌표계로 변경한다.
  • 입력되는 논리좌표계를 사용자 임의대로 변경한다. 즉 여기에서는 논리적으로 1um 를 표현하기 위해 GDI의 그리기 함수의 좌표에 항상 1000 값을 곱한다



------------------------------- test.cpp -------------------------------
DWORD tick = ::GetTickCount();

HWND hwnd = this->Handle;
HDC hdcFront = ::GetDC( hwnd );

RECT rect;
::GetClientRect(hwnd, &rect);
HDC hdc = ::CreateCompatibleDC( hdcFront);
::SetGraphicsMode(hdc,  GM_ADVANCED);
HBITMAP hbitmap = ::CreateCompatibleBitmap(hdcFront, rect.right, rect.bottom);
HBITMAP holdbitmap = (HBITMAP)::SelectObject( hdc, hbitmap);


// clear background with black
::SetROP2(hdc, R2_COPYPEN);
::SetMapMode(hdc, MM_TEXT);
HBRUSH hbrush = ::CreateSolidBrush( RGB(0,0,0) );
HBRUSH holdbrush = (HBRUSH)::SelectObject(hdc, hbrush);
::Rectangle(hdc, 0, 0, this->ClientWidth , this->ClientHeight);
::SelectObject(hdc, holdbrush);
::DeleteObject(hbrush);

// scale and origin location
double scale = 1.0;
POINT center;
center.x = this->ClientWidth /2;
center.y = this->ClientHeight /2;

::SetMapMode(hdc,  MM_ISOTROPIC);
::SetWindowExtEx(hdc, (int)(this->ClientWidth/(scale/1000.f)) , (int)(this->ClientHeight/(scale/1000.f)) , NULL);
::SetViewportExtEx(hdc, this->ClientWidth , -this->ClientHeight, NULL);
::SetViewportOrgEx(hdc, center.x, center.y, NULL);

// draw shapes
srand( ::GetTickCount() );
HPEN hpen = ::CreatePen(PS_SOLID, 0, RGB(rand()%255, rand()%255, rand()%255) );
HPEN holdpen = (HPEN)::SelectObject(hdc, hpen);
for (int i=0; i< 10000; i++)
{
    ::Rectangle(hdc,
        1000*(rand() % 200 -100),
        1000*(rand() % 200 -100),
        1000*(rand() % 200 -100),
        1000*(rand() % 200 -100)
        );

}

::SelectObject(hdc, holdpen);
::DeleteObject(hpen);

////////////////////////////////////////////////////////////////////////////

// revert transformation
::SetMapMode(hdc,  MM_TEXT);
::SetWindowExtEx(hdc, this->ClientWidth , this->ClientHeight, NULL);
::SetViewportExtEx(hdc, this->ClientWidth, this->ClientHeight, NULL);
::SetViewportOrgEx(hdc, 0,0, NULL);

// swapping font/back buffers
::BitBlt(hdcFront, 0, 0,  rect.right, rect.bottom, hdc, 0, 0, SRCCOPY);
::SelectObject(hdc, holdbitmap);

// clean up
::DeleteObject( hbitmap);
::DeleteDC(hdc);
::ReleaseDC( hwnd, hdcFront);

tick = ::GetTickCount() - tick;

ShowMessage("rendering time(msec) " + IntToStr((int)tick));

------------------------------------------------------------------------------


위 소스는 C++ Builder 기반으로 작성되었으며, OnPaint 이벤트에 적용하면 동작이 가능하다. 또한 MFC 나 기타 환경에서도 VCL 함수부분만 삭제하면 API를 통해 동작이 가능하다.
더블 버퍼링 구현을 위한 순서는 아래와 같다.


  • 후면 버퍼(hdc)의 DC에 검은색으로 브러쉬를 칠한다.
  • 후면 버퍼(hdc)의 확대. 중심위치등 변환행렬을 변경한다.
  • 후면 버퍼(hdc)에 각종 그리기 명령을 실시한다. 
  • 후면 버퍼(hdc)의 변환행렬을 초기화 한다.
  • 후면 버퍼(hdc)의 내용을 전면버퍼(hdc front)로 뿌려준다 (bitblt)


기타 현재 마우스가 위치한 픽셀 좌표가 실제 논리좌표로 어떤 위치인지를 알기 위해서는 Mouse Move 이벤트에 아래의 pseudo code 를 사용하면 얻어올수있다.


  • 후면버퍼(hdc)에 적용되었던 변환행렬을 호출한다.
  • DP2LP / LP2DP 등 함수를 이용하여 위치 정보를 역연산하여 얻어온다.


댓글 없음:

댓글 쓰기

시리우스 라이브러리 홈페이지 오픈

현재 시리우스(Sirius) 라이브러리라는 제품을 개발하고 이를 소개하는 홈페이지를 오픈 하였습니다. 관심있는 분들의 많은 방문 요청드립니다. 앞으로 업데이트 소식및 변경사항은 스파이럴랩 홈페이지를 통해 진행할 예정입니다. 스파이럴랩 홈페이지 :  h...