Creating an OpenGL Context (WGL)

From OpenGL.org
(Redirected from Creating an OpenGL Context)
Jump to: navigation, search

OpenGL Context Creation is the part of initialization that creates a fully realized OpenGL implementation. You need to go through this process to use OpenGL.

A Note on Platforms

Because OpenGL doesn't exist until you create an OpenGL context, OpenGL context creation is not governed by the OpenGL Specification. It is instead governed by platform-specific APIs. The following discussion will cover Windows-based initialization. GLX has its initialization functions as well; some of them have analogs in Windows, and some do not. Many of the Windows-specific initialization functions have the "wgl" prefix affixed to them.

This also assumes you know how to handle the Win32 API at some basic level of competence. You should know what a window handle (HWND) and a device context (DC) are, as well as how to create them. This is not a tutorial on how to create a Window.

Simple Context Creation

This section covers the basics of context creation.

The Window Itself

When you create your HWND, you need to make sure that it has the CS_OWNDC set for its style.

Pixel Format

Each window in MS Windows has a Device Context (DC) associated with it. This object can store something called a Pixel Format. This is a generic structure that describes the properties of the default framebuffer that the OpenGL context you want to create should have.

Setting up the pixel format is non-intuitive. The way you create a pixel format is that you fill out a struct that describes the features you want. Then you give that struct to a function that will return a number that represents the closest match that it can find in the list of supported pixel formats. You then set this number to be the pixel format of the DC.

The struct described above is the PIXELFORMATDESCRIPTOR. A good way to set this up is as follows:

    PIXELFORMATDESCRIPTOR pfd =
    {
        sizeof(PIXELFORMATDESCRIPTOR),
        1,
        PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER,    //Flags
        PFD_TYPE_RGBA,            //The kind of framebuffer. RGBA or palette.
        32,                        //Colordepth of the framebuffer.
        0, 0, 0, 0, 0, 0,
        0,
        0,
        0,
        0, 0, 0, 0,
        24,                        //Number of bits for the depthbuffer
        8,                        //Number of bits for the stencilbuffer
        0,                        //Number of Aux buffers in the framebuffer.
        PFD_MAIN_PLANE,
        0,
        0, 0, 0
    };

As you can see, many of the fields in the struct are set to 0. Leave them that way. The ones we need to be concerned about, the ones you might want to use, are labled above with comments. There are more flags than are specified in this pixel format; more information on them can be found in the Windows SDK documentation. These will do for now.

Now that we have a PIXELFORMATDESCRIPTOR, we need to convert this into a pixel format number. We do this with the function ChoosePixelFormat. This function takes a device context and PFD struct and returns a pixel format number. If it returns 0, then it could not find a pixel format that matches the description, or the PDF was not filled out correctly.

Once you have the pixel format number, you can set it into the DC with SetPixelFormat. This function takes the DC, the pixel format number, and a PFD struct pointer. Don't get excited about being able to supply the PFD struct; it doesn't read any important information out of it to set the pixel format into the context.

Create the Context

Once you have set pixel format in the DC, creating the context is easy. You call wglCreateContext. This function takes the DC as a parameter and returns a handle to the the OpenGL context (of type HGLRC, for handle to GL Rendering Context).

Before you can use OpenGL, the context you created must be made current. This is done with the wglMakeCurrent function. This takes a DC and the HGLRC context. If there is already a current context, then this function will cause the old context to be replaced with the new. OpenGL functions after this will refer to state in the new context, not the old one. If you pass NULL for the context, then the old one is removed and OpenGL functions will fail (or crash) as though you had never made a context current.

The current context is thread-specific; each thread can have a different context current, and it's dangerous to have the same context current in multiple threads.

Delete the Context

Technically not part of creation, but you should know how to delete a context.

The first step is always to make sure that the context you want to delete is not current. Call wglMakeCurrent with NULL for the context.

Now that the context is not current, you can call wglDeleteContext on it.

Proper Context Creation

Unless you are making a very simple application, you should not use the above simple context creation steps. There are a number of WGL extensions that give you greater power and flexibility in creating contexts. But to get access to those extensions, you have to make context creation a bit more complex.

Create a False Context

The key problem is this: the function you use to get WGL extensions is, itself, an OpenGL extension. Thus like any OpenGL function, it requires an OpenGL context to call it. So in order to get the functions we need to create a context, we have to... create a context.

Fortunately, this context does not need to be our final context. All we need to do is create a dummy context to get function pointers, then use those functions directly. Unfortunately, Windows does not allow the user to change the pixel format of a window. You get to set it exactly once. Therefore, if you want to use a different pixel format from the one your fake context used (for sRGB or multisample framebuffers, or just different bit-depths of buffers), you must destroy the window entirely and recreate it after we are finished with the dummy context.

A good pixel format to choose for the dummy context is a simple 32-bit RGBA color buffer, with a 24-bit depth buffer and 8-bit stencil, as we did in the above sample PFD. This will usually get a hardware accelerated pixel format.

So, this step means going through the above code to create a context. Make it current as well.

Get WGL Extensions

If you are using an extension loading library, now is the time to call whatever function is required to have it load function pointers of interest. If you are not using an extension loading library, then you will need to do this manually.

There are quite a few extensions of interest for doing advanced context creation. Most of them revolve around pixel format creation, with one notable exception.

Pixel Format Extensions

The PFD struct is a nice way to describe your needs to the OpenGL implementation. But it does have one major flaw; it isn't extensible. Therefore, there is the WGL_ARB_pixel_format extension. This extension defines a new mechanism for getting a pixel format number, one based on providing a list of attributes and values.

To use this, the extension must be defined. Much like WGL_ARB_extensions_string, this one has been around for a long time, and even old implementations will provide it. So if you've gotten this far, it's a good bet that WGL_ARB_pixel_format is implemented too.

There are several new functions in this extension, but the one we are interested in is this one:

BOOL wglChoosePixelFormatARB(   HDC hdc,
                                const int *piAttribIList,
                                const FLOAT *pfAttribFList,
                                UINT nMaxFormats,
                                int *piFormats,
                                UINT *nNumFormats);


wglChoosePixelFormatARB is analogous to ChoosePixelFormat. Instead of taking a fixed PFD struct, it takes a list of attributes and values. Many of these attributes have direct analogs to PFD struct fields, but some of them are new. Also, unlike ChoosePixelFormat, this function can return multiple formats that fill the requested parameters. The order of these is in order from best fits to worst, though what constitutes "best" is implementation-defined.

In any case, the way it works is fairly simple. piAttribIList is a list of integer attributes. Every two elements in the list is an attribute/value pair. The attribute "0" represents the end of the list, and it doesn't need a value after it. You can pass NULL if you wish; this function will act as if you passed an empty list.

Similarly, pfAttribFList is a list of floating-point attributes. Every two elements in the list is an attribute/value pair. How do you put the attributes (which are integers) in a float list? Very carefully. You need to static-cast them (if you're using C++) or do other trickery to make C keep the bit-pattern between the integer and float form the same.

The nMaxFormats is the maximum number of formats that will be stored in piFormats. Therefore, piFormats should be a list of at least that many entries. The nNumFormats is a return value, informing you how many entries were stored in the list.

If this function returns FALSE (not GL_FALSE, but the Windows FALSE. Both are just 0, though), then the code failed to find an appropriate pixel format. Despite not finding a pixel format, the piFormats list is left in an undefined state (translation: the implementation is free to change stuff in it even if it failed). If the return value is not FALSE, the function worked and you have pixel format numbers.

Here is an example of this function that should produce a near-equivalent list of pixel formats as our above code:

const int attribList[] =
{
    WGL_DRAW_TO_WINDOW_ARB, GL_TRUE,
    WGL_SUPPORT_OPENGL_ARB, GL_TRUE,
    WGL_DOUBLE_BUFFER_ARB, GL_TRUE,
    WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB,
    WGL_COLOR_BITS_ARB, 32,
    WGL_DEPTH_BITS_ARB, 24,
    WGL_STENCIL_BITS_ARB, 8,
    0,        //End
};

int pixelFormat;
UINT numFormats;

wglChoosePixelFormatARB(hdc, attribList, NULL, 1, &pixelFormat, &numFormats);

There are a number of extensions that have added new attributes for this function. The important ones that you might want to use are:

Once you have a pixel format number, you can set it just like any pixel format with SetPixelFormat.

Create Context with Attributes

OpenGL 3.0 and above created a deprecation and removal model for getting rid of old, legacy functionality. However, it also created a bit of a problem. In previous OpenGL versions, the new version was a strict superset of the old. Therefore, if you wanted a 1.5 context and got a 2.0 context, that was fine; you just got extra functionality you didn't use. Once the possibility of removing old functionality came into being, that was no longer viable.

Thus, the extension WGL_ARB_create_context was made. It exposes a new function to replace wglCreateContext. Much like wglChoosePixelFormatARB, it adds an extensibility mechanism to the system that makes it possible to extend the options for context creation.

If the fake context does not expose this extension, then you cannot use this section. You must use wglCreateContext as normal.

If it does advertise this extension, then there are a number of features that we can access that would normally not be available:

  • Ensure getting an OpenGL 3.0 or greater context.
  • Creating an OpenGL 3.2 or greater core context, without the compatibility features.
  • Creating a context without a window, for off-screen rendering. This may not actually work.
Legacy Note: Implementations that support GL 3.0 or 3.1, but not 3.2 used a slightly different scheme for context creation than those that support 3.2. Pre 3.2 implementations were required to ask for a GL 3.0 or greater context in order to get one; thus, you had to use the new creation API to get a higher GL version. 3.2 and above do not; they can get backwards-compatible profiles of 3.0 or greater versions (assuming the implementation supports them). Thus, the best way to ensure that you get 3.0 or above is to ask for it with this extension. As more drivers implement GL 3.2, this will become less of an issue.
You can tell the difference by checking the extensions. If WGL_ARB_create_context_profile is defined, then it uses the above method. If it is not, then the only way to get a GL 3.0 or greater context is to use wglCreateContext directly.

The signature for wglCreateContextAttribsARB is as follows:

HGLRC wglCreateContextAttribsARB(HDC hDC, HGLRC hshareContext, const int *attribList);

The attribList works similarly to the one in wglChoosePixelFormatARB. It is a series of attribute/value pairs, with a 0 attribute signaling the end of the list.

You can ask for a specific version of OpenGL by using the two attributes WGL_CONTEXT_MAJOR_VERSION_ARB and WGL_CONTEXT_MINOR_VERSION_ARB. How this is resolved is complicated.

There are a number of rules that define what version you get back when you ask for a specific version. The rules are complicated, but boil down to two things:

  1. It will always return an OpenGL version equal to or greater than the one you ask for.
  2. It will never return an OpenGL version and profile that does not implement core features that the version you ask for implements.

If the extension WGL_ARB_create_context_profile is defined, then you can also use the WGL_CONTEXT_PROFILE_MASK_ARB to select a core profile (WGL_CONTEXT_CORE_PROFILE_BIT_ARB) or a compatibility profile (WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB). Note that these are bits, so you could ask for both (but it would simply return a compatibility one). The details of what this means merit a longer discussion.

You can also pass a number of flags with the WGL_CONTEXT_FLAGS_ARB. With these, you can ask for a forward compatible context (WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB) and/or a debug context (WGL_CONTEXT_DEBUG_BIT_ARB). A debug context will often implement ARB_debug_output for enhanced error message testing. A forward compatible context must fully remove deprecated features in the version that it returns; you should never actually use this.

The hshareContext is a special field. If you have two GL contexts, and you want them to share objects, then you can use the function wglShareLists. But you have to do this before you create objects in either context. wglCreateContextAttribsARB incorporates this functionality directly into context creation.

Sample Code: Create Render Context, Check GL_VERSION

Heres a working program which creates a render context and shows the version number in a messagebox, then shuts down the program:

#include <windows.h>
#include <GL/GL.h>

#pragma comment (lib, "opengl32.lib")

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);

int WinMain( __in HINSTANCE hInstance, __in_opt HINSTANCE hPrevInstance, __in_opt LPSTR lpCmdLine, __in int nShowCmd )
{
        MSG msg          = {0};
        WNDCLASS wc      = {0}; 
        wc.lpfnWndProc   = WndProc;
        wc.hInstance     = hInstance;
        wc.hbrBackground = (HBRUSH)(COLOR_BACKGROUND);
        wc.lpszClassName = L"oglversionchecksample";
        wc.style = CS_OWNDC;
        if( !RegisterClass(&wc) )
                return 1;
        CreateWindowW(wc.lpszClassName,L"openglversioncheck",WS_OVERLAPPEDWINDOW|WS_VISIBLE,0,0,640,480,0,0,hInstance,0);

        while( GetMessage( &msg, NULL, 0, 0 ) > 0 )
                DispatchMessage( &msg );

        return 0;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
        switch(message)
        {
        case WM_CREATE:
                {
                PIXELFORMATDESCRIPTOR pfd =
                {
                        sizeof(PIXELFORMATDESCRIPTOR),
                        1,
                        PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER,    //Flags
                        PFD_TYPE_RGBA,            //The kind of framebuffer. RGBA or palette.
                        32,                        //Colordepth of the framebuffer.
                        0, 0, 0, 0, 0, 0,
                        0,
                        0,
                        0,
                        0, 0, 0, 0,
                        24,                        //Number of bits for the depthbuffer
                        8,                        //Number of bits for the stencilbuffer
                        0,                        //Number of Aux buffers in the framebuffer.
                        PFD_MAIN_PLANE,
                        0,
                        0, 0, 0
                };

                HDC ourWindowHandleToDeviceContext = GetDC(hWnd);

                int  letWindowsChooseThisPixelFormat;
                letWindowsChooseThisPixelFormat = ChoosePixelFormat(ourWindowHandleToDeviceContext, &pfd); 
                SetPixelFormat(ourWindowHandleToDeviceContext,letWindowsChooseThisPixelFormat, &pfd);

                HGLRC ourOpenGLRenderingContext = wglCreateContext(ourWindowHandleToDeviceContext);
                wglMakeCurrent (ourWindowHandleToDeviceContext, ourOpenGLRenderingContext);

                MessageBoxA(0,(char*)glGetString(GL_VERSION), "OPENGL VERSION",0);

                wglDeleteContext(ourOpenGLRenderingContext);
                PostQuitMessage(0);
                }
                break;
        default:
                return DefWindowProc(hWnd, message, wParam, lParam);
        }
        return 0;

} 

See Also

References