Friday, May 20, 2016

Minimal Code for Creating An OpenGL Context

Background

Anyone who does any work with OpenGL in a Windows environment knows it is necessary to create an OpenGL context before OpenGL extensions can be loaded. The typical instruction is first create a temporary window, then a temporary OpenGL context which must be made current.  Once that is done, initialize the OpenGL extensions using your favorite extension loading library like GLEW or GLEE or whatever. If that succeeds, you will have the necessary functions to take control of the context creation parameters using the newer wglChoosePixelFormatARB and similar functions. Attach the desired context to the application window, make it current and then initialize the extensions loader again.  At that point the temporary window may be destroyed. It seems like an odd series of steps to be sure, but if you think about it, extensions are associated with a context and the "wgl" functions in particular are associated with a Windows device context (HDC) which means all of these things need to be setup in advance.

Lately, I have been playing with a homegrown extension loader written in "C". In order to test the functionality of my loader, I needed to create a Window and OpenGL context. Those who create windows from scratch understand, that while straight-forward, it is not necessarily trivial. I learned several years ago the temporary window does not need to be made visible, for the process of context creation to work. Also, for my purposes of checking my extension loading library, it was not necessary for me do anything but the bare minimum in order to create an OpenGL context. Here is how I did it.

The 'main' Function

I rarely use the expected WinMain function when creating a windows application. It doesn't seem to give me any real advantages and it isn't necessary in order create a main application Window.  The only possible advantage is suppression of the console window.  But, for me, the console window provides a mechanism for entering commands and outputting messages, so I prefer to have one for much of what I do.

int main(int argc, char* argv[])
{
 if (!CreateContext())
  return -1;
 int retv = 0;
 if (!initOpengl()) // should succeed if context exists
  retv = 1;
 RemoveContext();

 return retv;
}


The key functions for context management are the calls to CreateContext, which creates the temporary window and OpenGL context, and RemoveContext which destroys them in the text-book fashion.

The Context Functions

The CreateContext function performs three tasks.  It registers a new window class, creates a window of the class, and then attaches an OpenGL context to it.

HWND      hwnd;
HINSTANCE hinst;
HDC       hdc;
HGLRC     hglrc;

bool32 CreateContext()
{
 hinst = GetModuleHandle(0);
 WNDCLASS wclass      = { 0 };
 wclass.style         = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
 wclass.hInstance     = hinst;
 wclass.lpszClassName = "TEST_CLASS";
 wclass.lpfnWndProc   = (WNDPROC)MsgHandler;
 if (!RegisterClass(&wclass))
  return false;
 hwnd = CreateWindow("TEST_CLASS", "TEST", 0, 0, 0, 256, 256, 0, 0, hinst, 0);
 if (!hwnd)
  return false;

 hdc = GetDC(hwnd);

 PIXELFORMATDESCRIPTOR pfd = { 0 };
 pfd.nSize        = sizeof(PIXELFORMATDESCRIPTOR);
 pfd.nVersion     = 1;
 pfd.dwFlags      = PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER | PFD_DRAW_TO_WINDOW;
 pfd.iPixelType   = PFD_TYPE_RGBA;
 pfd.iLayerType   = PFD_MAIN_PLANE;
 pfd.cColorBits   = 32;
 pfd.cDepthBits   = 24;
 pfd.cStencilBits = 8;
 int pf = ChoosePixelFormat(hdc, &pfd);
 if (!pf) {
  return false; // returns null
 }
 if (SetPixelFormat(hdc, pf, &pfd)) {
  hglrc = wglCreateContext(hdc);
 }
 if (hglrc) {
  if (!wglMakeCurrent(hdc, hglrc))
   return false;
 }
 return true;
}

Notice that the WNDCLASS struct requires a pointer to a message handler.  This function only requires a few lines of code.

int32 MsgHandler(HWND hwnd, uint32 uMsg, uint32 wParam, uint32 lParam)
{
 switch (uMsg)
 {
 case WM_DESTROY:
  return 0;
 }
 return DefWindowProc((HWND)hwnd, uMsg, wParam, lParam);
}


Finally, when the temporary context is no longer required, we clean up after ourselves.

void RemoveContext()
{
 wglMakeCurrent(0, 0);
 wglDeleteContext(hglrc);
 DestroyWindow(hwnd);
 UnregisterClass("TEST_CLASS", hinst);
}

Next Steps

In the 'main' function, between CreateContext and RemoveContext, the call is made to initialize the OpenGL extensions.  If this succeeds, this would be the time to take the next important step of using the extended functions to create the final desired context. Depending on your purposes and how the final context is created, you may need to alter the RemoveContext function since it calls wglMakeCurrent(0, 0) which may remove your newly created context from scope.


No comments:

Post a Comment