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.


Wednesday, May 18, 2016

Supporting Opengl Through Version 3.3

Introduction

I love to write code (both C++ and C) which utilizes Opengl for graphics. However, many of the computers I target are not high-end game machines. Most of the time, they are common, lower-cost systems or business machines which rarely support Opengl versions above 3.2. Today, it seems most of the online documentation and advice assumes you are targeting the most recent versions of Opengl and often I have seen the advice, "download the latest drivers". But it simply doesn't work that way. Many computers are incapable of supporting state-of-the-art graphics without sinking a sizable chunk of money into new cards.


Loading Extensions

Usually, when writing Opengl code, I use GLEW in order to avoid the pain and possible errors of trying to create my own Opengl extension loader.  Most of the projects I create use static linking and in order to statically link GLEW, the best way to avoid certain problems with Visual Studio is to build GLEW from source.  This works well once the proper "make" system is used to create the necessary project files. Recently, I have decided to fore go GLEW and write my own extension loader as an educational exercise.  I thought it would be convenient to target a limited subset of the Opengl functions and definitions aimed at supporting low-end graphics.  I was able to figure out the process looking at various online sources and within a few hours, had a working extension loader which allowed me to create a version 3.0 context.


Opengl Hell

As I tried to expand the extension loading library I began the process of digging much deeper under the hood of Opengl, trying to understand which functions and enum values are required for various versions of Opengl. Once you dive-in, you soon discover the documentation may be scattered around and not always clear. For example, consider the extensions required to support shaders in Open version 2.0. You will need to derive the functions and definitions from extension, #30, GL_ARB_shader_objects; #31, GL_ARB_vertex_shader; #32 GL_ARB_fragment_shader; and #33, GL_ARB_shading_language_100.  However, you will soon discover the final function names are not the same as the onces defined in the extension specifications (see: https://www.opengl.org/registry/). So which functions are supported in "official" Opengl 2.0?  How do I know which functions and definitions are considered "core profile", especially considering certain enum values and functions are deprecated in version 3.1?  Sure, as they say, deprecated does not mean deleted but that statement is usually followed by "but...it is best not to write code using deprecated functions because you never know when the functions and values will be deleted". So if you want your legacy code to be viable in the next decade, avoid deprecated functions.  No problem...so which enumerants are deprecated? Ah! Welcome to Opengl hell, at least it was for me.

Shedding Some Light

Nevertheless, despite the lack of clarity I decided to begin assembling the information I needed to build an extension loading library. So, over the course of a few days, I rummaged through various specifications and sources and assembled a spreadsheet which went a long to way toward giving me the information I needed. On the spreadsheet I listed all known Opengl functions and enumerants and recorded which are considered core, which are deprecated, and in which extensions they are declared. Because, it was not easy to put this together, I decided to make the next person's workload a little easier and have posted it online here.  Hopefully you will find if of value. Later I will expand the information to include versions 4.0 and greater.