본문 바로가기

내가 작업한 것들

OpenGL 을 window DC 에 렌더링 하기 (Off-screen rendering)

뮬레이터 라던가 , 이런 저런 이유로 OpenGL 을 활성화 된 window DC 에 그리지 않고 , 별도의 다른 컴퍼넌트와 함께 사용하거나 , OpenGL 로 렌더링 된 결과물을 가져와서 다른 가공 처리를 하고 싶을때가 간혹 많습니다.

그럴려면 OpenGL 렌더링을 Window DC 에 바로 하지 않고 처리를 해야 하는데 .. 이런 처리는 정말 찾기가 어렵죠.

그래서 온갖 방법을 다 동원 하다가 다음과 같이 방법을 찾았습니다.

이 방법은 현재 DEV-C++ 에서만 사용해 보았습니다만 , 응용 한다면 Delphi 에서도 사용이 가능해 보입니다.
실제 바로 사용으로는 delphi 에서 DC 에 바로 그리질 못하더군요 (아마 VCL 때문에 다른 차이점이 있어 보입니다.)

다음은 코드 입니다.
간단히 windows 를 생성 한 다음 바로 이 함수를 window Handle 로 넘겨 주게 되면 전역변수로 선언 해 둔 hdcOpenGL 에 OpenGL 렌더링 물이 그려지게 됩니다.

HDC     hdcOpenGL;
HGLRC   hrcOpenGL;
HBITMAP hbmOpenGL;

void InitOpenGLContext(HWND hWnd, int nWidth, int nHeight)
{
    HDC hdcWin = GetDC(hWnd);
    hdcOpenGL  = CreateCompatibleDC(hdcWin);
    hbmOpenGL  = CreateCompatibleBitmap(hdcWin, nWidth, nHeight);
    SelectObject(hdcOpenGL, hbmOpenGL);
    ReleaseDC(hWnd, hdcWin);
    
    BITMAP bmInfo;
    GetObject(hbmOpenGL, sizeof(BITMAP), &bmInfo);

    PIXELFORMATDESCRIPTOR pfd;
    ZeroMemory(&pfd, sizeof(pfd));
    pfd.nSize      = sizeof(pfd);
    pfd.nVersion   = 1;
    pfd.dwFlags    = PFD_DRAW_TO_BITMAP | PFD_SUPPORT_OPENGL | PFD_SUPPORT_GDI;
    pfd.iPixelType = PFD_TYPE_RGBA;
    pfd.cColorBits = (BYTE)bmInfo.bmBitsPixel;
    pfd.cDepthBits = 8;
    pfd.iLayerType = PFD_MAIN_PLANE;
    int nFormat    = ChoosePixelFormat(hdcOpenGL, &pfd);
    SetPixelFormat(hdcOpenGL, nFormat, &pfd);
    
    hrcOpenGL = wglCreateContext(hdcOpenGL);
    wglMakeCurrent(hdcOpenGL, hrcOpenGL);
}

여기서 중요한 것은 PIXELFORMATDESCTIPTOR 에 적용되는 cColorBits 입니다.
이 cColorBits 가 BITMAP 으로 가져온 정보의 bmBitsPixel 로 사용하지 않으면 wglMakeCurrent() 에서 그리기 버퍼용 DC 를 openGL 용으로 만들수 없게 된다는 점 입니다. (FALSE 가 return 됩니다)

또한 DC 에 그리므로 dwFlags 에 PFD_DOUBLEBUFFER 를 선언 할 필요는 없습니다.

이렇게 한 다음 OpenGL API 를 이용하여 렌더링을 한다음 반드시 glFlush(); 를 호출해 주시기 바랍니다.
하드웨어 가속으로 그릴때와 달리 버퍼에 그리는 작업은 CPU 가 하기 때문에 glFlush() 를 하지 않고 DC 를 swap 하게 되면 다 그려지지 않은 상태로 렌더링 되는 경우가 많기 때문 입니다.

렌더링을 마치고 glFlush() 로 현재 매트릭스 내의 모든 연산과 렌더링을 마치면 이제 그려진 DC 를 필요로 하는 곳으로 복사 하는 일이 남았습니다.

예제로 저는 windowDC 에다가 BitBlt() 함수로 복사 하는 것을 만들어 보았습니다.

       HDC hdcWin = GetDC(hWnd);
       BitBlt(hdcWin, 
      5, 
  5, 
  OPENGL_SURFACE_WIDTH + 5, 
  OPENGL_SURFACE_HEIGHT + 5, 
  hdcOpenGL, 
  0, 
  0, 
  SRCCOPY);
       ReleaseDC(hWnd, hdcWin);

예제 프로그램은 windows API 로만 만들었기 때문에 WinMain callback 내부에서 while(1){ ... } 루프내에 메시지를 grab 하면서 계속 그리도록 만들었습니다.
WinMain 에서 그리게 되면 ON_PAINT 이벤트를 받지 않고 지속적으로 그릴수 있기 때문에 이상하게 렌더링 되는 문제를 해결 할 수 있습니다.

CPU 로 렌더링 하는 것인지 .. 아니면 비디오 하드웨어가 버퍼에 그리면 느린것인지는 확실 하지 않습니다만 렌더링이 매우 느립니다.
예제로 만들어 본 것으로는 240x320 해상도로 그리는 것과 640x480 으로 그리는 것 모두 Core2Duo E6550 기준으로 한쪽 코어가 50~60% 정도 사용율이 올라가게 됩니다.
해상도엔 크게 차이가 없어 보이구요 ... 같은 렌더링을 그릴 경우 화면에 따라 다르지만 대충 10~20fps 로 그리게 됩니다.

Off-screen 으로 그리는 것은 별도로 OpenGL 로 렌더링 된 결과물을 가져다 다른 이미지와 함께 쓰고 싶을때 사용하는 것이 더 나을지도 모른다는 생각이 드는 결과 였습니다.

이번 테스트에 대해서는 예제 샘플을 제공할 수가 없어서 소스를 공유 하지 못하는 것에 혹시나 소스가 필요하신 분들에게 죄송합니다. (하지만 중요한건 위에 다 설명 되어 있습니다 ^^)