Tutorial 1 (Creating an OpenGL Context)

|

Outline

Many OpenGL tutorials start with something like “First install GLFW”, or “To start you will need a windowing library such as SDL”, or if you’re really unlucky, “Start by downloading Freeglut”. None of these are necessary (although if you insist on using one I recommend SFML which provides lots of nice features such as image/audio loading and has a very shallow learning curve). The one advantage these libraries offer you is that they tend to be extremely portable and will run across most platforms. I suspect that if you are reading a tutorial on getting started with OpenGL then you’re probably some way from worrying about cross-platform compatibility, and the benefits of doing it all yourself can arguably outweigh the costs.

Benefits

  • No external dependencies
    • This makes it easier to share your code with others and involves less faffing about setting up the project.
  • You have a better understanding of how everything in your project works
    • This is generally a ‘Good Thing’.
  • Most off these libraries do more than just provide an OpenGL context, they provide all kinds of things like image loading, input handling, font loading, vector/matrix classes and much more.
    • This is all useful stuff, but the more of it you choose to use the more it will influence the style and architecture of your own applications. This in itself isn’t a bad thing, however being aware of how this stuff works helps you make better decisions if you do decide to go for a third party solution.
  • These libraries themselves have API’s that you will need to learn. For most of them it is possible to create a window with an OpenGL context with just a couple of lines of code, and if that’s all you want then that would be OK, but the other features (particularly input handling which is generally closed tied to the window), are always tempting to use and it can be quicker to just role your own and only implement what you actually need.

Costs

  • It takes time to write your own windowing code and attach an OpenGL context.
    • This is especially true if you want it to be robust.
  • Win32 is awful.
    • Really, it’s just awful.
  • If you want to target another problem, you will have to do this all again.
    • In order to create cross-platform libraries, you do at some point need to write platform specific code. Some people will say you don’t need to know Win32 as you should only write cross platform code. If you want to do anything graphical then at some point platform specific code will be required. It may be hidden from you inside a 3rd party library such as SDL or SFML, but they do have various sections of code that are just pre-processor conditionals checking for platform type. It is nice to have at least a basic understanding of how this stuff works.

Method

To start, create a new Visual Studio project (if you are using a different tool chain then do the equivalent), call it whatever you like, but for this tutorial I’m going to call mine Tutorial1. (Personally I go for a console application with pre-compiled header, but you can go without the pre-compiled header if you like, I mainly just use them out of habit as most projects I work on actually benefit from them). In your new project you will see a number of files have been generated:

  • stdafx.h
  • targetver.h
  • YourProjectName.cpp (For my project that will be Tutorial1.cpp)
  • stdafx.cpp

stdafx.h and stdafx.cpp make up your pre-compiled header, targetver.h can be safely ignored as well, the block comment it contains provides all the explanation that’s really needed.

You may see both Win32 and x64 configurations have been created, these tutorials assume that they run under the x64 configuration, I generally just delete the Win32 one as soon as I remember.

Tutorial1.cpp defines a main function in the Microsoft way:

int _tmain(int argc, _TCHAR* argv[])
{
    return 0;
}

Delete this and replace it with the more traditional:

int main( int argc, const char* argv[] )
{
    return 0;
}

NOTE: This tutorial was originally created using VS 2013. I now use VS 2015 and the generated main looks like:

int main()
{
    return 0;
}
>```

as we don’t actually use the input parameters for the main method this is fine as it is.

We will also need a couple of windows header files as well as maing sure we link against the opengl library, add the following after #include "stdafx.h":

```c++
#define WIN32_LEAN_AND_MEAN  // Exclude rarely-used stuff from Windows headers
// Windows Header Files:
#include <windows.h>
#include <windowsx.h>

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

I like to have my main function in a file called main.cpp so I’m going to rename that as well.

As much as possible we will try and lessen the awfulness of Win32 by avoiding its endless macros and defines (macros are always a ‘Bad Thing’). You don’t even really need the parameters unless you plan on passing in command line parameters to your application (and this tutorial won’t cover any off that) but it is nice to at least see the “Standard” entry point definition.

The first thing that has to be done is we need to create a window. I’m not going to go into too much detail about this, there’s loads of stuff about windows and how to create them on the internet already, however there are a couple of pitfalls to be careful off.

To start with we will just do everything inside main().

The first thing you need is a Window Handle (HWND):

HWND window_handle;

An HWND is initialised by calling: CreateWindow(...); Unfortunately, as with all things windows, it’s not that straightforward. In order to create the window you will need a HINSTANCE and a WNDCLASSEX. The HINSTANCE can be created by calling:

HINSTANCE instance = GetModuleHandle(nullptr);

The HINSTANCE is a handle to an instance. That is to say the base address of the module in memory. For more info on HINSTANCE click here: The WNDCLASSEX is slightly more complex. It’s a struct contains information about your window and contains the following fields:

struct WNDCLASSEX {
    UINT      cbSize; // This is always this sizeof(WNDCLASSEX)
    UINT      style;  // For an OpenGL window this must be CS_OWNDC
    WNDPROC   lpfnWndProc;  // Function pointer to a function that deals with windows messages
    int       cbClsExtra;  // We don’t need this.
    int       cbWndExtra;  // We don’t need this.
    HINSTANCE hInstance;   // The HINSTANCE of this module
    HICON     hIcon;  // The icon that identifies the app. Set it to NULL for default
    HCURSOR   hCursor;  // The default cursor to be used when your window has focus
    HBRUSH    hbrBackground;  // The default background of the window
    LPCTSTR   lpszMenuName;  // The main application menu for the window
    LPCTSTR   lpszClassName;  // An identifier for this WNDCLASSEX (This has nothing to do with C++ classes)
    HICON     hIconSm;  // The small icon that identifies the app. Set it to NULL for default
}

For more details on what these all mean see MSDN:
We create a WNDCLASSEX as follows:

WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_OWNDC;
wcex.lpfnWndProc = DefWindowProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = instance;  // The instance we got above
wcex.hIcon = NULL;
wcex.hCursor = LoadCursor(NULL, IDC_ARROW); // Just use the arrow for now
wcex.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); // Set it to black
wcex.lpszMenuName = NULL;
wcex.lpszClassName = L"owp_window";  // The class name, this could be anything
wcex.hIconSm = NULL;

We then register the WNDCLASSEX by calling:

RegisterClassEx(wcex);

Now we have an HINSTANCE and a WNDCLASSEX we can create our window

window_handle = CreateWindow(
    L"tutorial_1_window",         // The class we created above
    L"Tutorial",                // The title of the window
    WS_BORDER | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX, // The style of the window
    200,                   // The x position of the top left
    200,                   // The y position of the window
    600,                   // The width of the window
    400,                   // The height of the window
    NULL,                  // The parent of the window
    NULL,                  // The menu for the window
    instance,              // The module instance
    NULL                   // Not sure don't worry about it
);

Now the window has been created we just need to tell it to show and to update so:

ShowWindow(window_handle, SW_SHOW);
UpdateWindow(window_handle);

And finally we need to deal with windows messages, otherwise it becomes unresponsive. This is pretty much boilerplate. We need to check for the message_return value otherwise the application won’t exit properly.

MSG msg;
BOOL message_return;
bool run_loop = true;
while(run_loop && (message_return = GetMessage(&msg, window_handle, 0, 0)) != 0)
{
    if(message_return == -1)
    {
        run_loop = false;
    }
    else
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
}
DestroyWindow(window_handle);  // Clean up after ourselves

So now we have a window, but we still don’t have an OpenGL context. The most basic OpenGL context is simple enough to do. We just need a PIXELFORMATDESCRIPTOR , an HDC (Device Context) and an HGLRC (OpenGLContext). (Note: All the following code should be added before the call to ShowWindow) We first need to create a PIXELFORMATDESCRIPTOR object, the PIXELFORMATDESCRIPTOR is a struct that has the following form.

struct PIXELFORMATDESCRIPTOR {
    WORD  nSize;
    WORD  nVersion;
    DWORD dwFlags;
    BYTE  iPixelType;
    BYTE  cColorBits;
    BYTE  cRedBits;
    BYTE  cRedShift;
    BYTE  cGreenBits;
    BYTE  cGreenShift;
    BYTE  cBlueBits;
    BYTE  cBlueShift;
    BYTE  cAlphaBits;
    BYTE  cAlphaShift;
    BYTE  cAccumBits;
    BYTE  cAccumRedBits;
    BYTE  cAccumGreenBits;
    BYTE  cAccumBlueBits;
    BYTE  cAccumAlphaBits;
    BYTE  cDepthBits;
    BYTE  cStencilBits;
    BYTE  cAuxBuffers;
    BYTE  iLayerType;
    BYTE  bReserved;
    DWORD dwLayerMask;
    DWORD dwVisibleMask;
    DWORD dwDamageMask;
}

Create an instance of PIXELFORMATDESCRIPTOR in the following way:

PIXELFORMATDESCRIPTOR pixel_format_desc =
{
    sizeof(PIXELFORMATDESCRIPTOR),
    1,
    PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER,
    PFD_TYPE_RGBA,
    32,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    24,
    8,
    0,
    PFD_MAIN_PLANE,
    0,
    0,
    0,
    0
};

Then get the HDC for the window:

GetHDC(window_handle);

And then get the best match pixel format:

int pixel_format = ChoosePixelFormat(device_context, &pixel_format_desc);

Set the pixel format:

SetPixelFormat(device_context, pixel_format, &pixel_format_desc);

And then finally make the OpenGL Context:

HGLRC  gl_context = wglCreateContext(device_context);

Once we have an OpenGL context we have to set it as the current context:

wglMakeCurrent(device_context, gl_context);

Also in the spirit of encouraging good coding practice we should delete the context before we exit (just before we destroy the window).

wglDeleteContext(gl_context);

And that’s it we have an OpenGL context.
But Wait…
What we actually have is a pretty crappy OpenGL context.
You may have noticed that all the functions dealing with OpenGL creation started with wgl, that’s because WGL is the windows API for interacting with OpenGL, on other OS’s you may use GLX, which provides similar functions. The base API is pretty basic and you can’t really do much, (I’ve never even tried to use it), so what we want to do is enable extensions, that way we can create more useful contexts. The first problem is that the call to enable extensions requires a context to be created. So what we do is create a simple context, enable the extensions, destroy the simple context, and then create a new and improved one. The best thing is we have just created a simple context so what do we need next? We need to enable extensions. There are loads of extensions, and it is possible to load them manually but generally it is better to use an extensions library. The most common one is GLEW, which is available as source or binary, linking to it can be a bit of a pain, so I find it easiest to just get the source and it directly to my own project. Just download the source, extract it and copy the glew.h and wglew.h files from the include\GL folder and glew.c from the src folder into a folder called GL beside your own source files.

You then need to add the files to your project:

And then add the path to the GLEW headers to your project properties.

I recommend using the built in Macros for Visual Studio paths wherever possible. It really does simplify things. Finally we need to turn pre-compiled headers off for glew.c.

It might be best just to build the project and run just now to make sure it still works. As long as everything still builds and we still get an empty window we can start to proceed with our improved context. To do this first need to add three includes at the top of the file (after the existing #includes):

#include "GL\glew.h"
#include "GL\wglew.h"
#include <iostream>

Then starting above the call to ShowWindow() we add:

glewExperimental = TRUE;
auto err = glewInit();
if(err != GLEW_OK)
{
    //Problem: glewInit failed, something is seriously wrong.
    std::cout << "glewInit failed, aborting." << std::endl;
    exit(0);
}
std::cout << "Using GLEW Version: " << glewGetString(GLEW_VERSION) << std::endl;
std::cout << "OpenGL: OpenGL version " << glGetString(GL_VERSION) << " supported" << std::endl;
wglMakeCurrent(nullptr, nullptr);
wglDeleteContext(gl_context);

We need to enable glewExperimental due to an ongoing bug in GLEW, then we call glewInit() , it seems fairly obvious that initializes GLEW. Then we destroy the simple context that we created. Before we destroy it though we have to make it not the current context. To do this just call wglMakeCurrent(nullptr, nullptr); (This isn’t strictly necessary as wglDeleteContext(gl_context) should make the context non-current before deleting it. But this makes it more obvious to the developer that this is happening). If we run the app now we should see something like (The version numbers will depend on your system):

Hopefully your supported OpenGL version is above 4.x.x or else some of the other tutorials I’m planning on writing might not work. In fact if your supported version is less than 3.1 then even this tutorial probably won’t work. We’ve enabled GLEW so now we can create our fancy context. The first thing we have to do is get a Pixel Format. This is similar to when we got a pixel format for the simple context, but this time we use the wglChoosePixelFormatARB extension. First we check that this has been enabled properly:


if (!WGLEW_ARB_pixel_format)
{
    std::cout << "Pixel Format Extension Not Supported; Exiting..." << std::endl;
    exit(0);
}

Then we define our list of requested attributes. Unlike the simple context we don’t populate a struct, instead we define a set of paired values. The first value is the attribute we want to define, the second is the value that we want to use. This can have an arbitrary number of values

const int pixel_format_attribute_list[] =
{
    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 of attributes list
};

Now we get the pixel format using wglChoosePixelFormat which has the following signature:

!#c++
BOOL wglChoosePixelFormatARB(
    HDC hdc,                      // The HDC of the window
    const int *piAttribIList,     // A list of integer attributes
    const FLOAT *pfAttribFList,   // A list of floating point
    UINT nMaxFormats,             // Number of pixel formats that will be returned
    int *piFormats,               // array of pixel formats from best match down
    UINT *nNumFormats             //  The number of pixel formats returned
);

We call it with the following parameters:

int improved_pixel_format;
UINT num_formats;
if(wglChoosePixelFormatARB(device_context,
                           pixel_format_attribute_list,
                           nullptr,                     // We don’t use this
                           1,                           // Just get the best match
                           & improved_pixel_format,
                           &num_formats) == 0)
{
    std::cout << "Couldn't find valid pixel format" << std::endl;
    exit(0);
}

We then call SetPixelFormat, unlike in the simple context case above we don’t need a PIXELFORMATDESCRIPTOR object and can just pass in a nullptr.

SetPixelFormat(device_context, improved_pixel_format, nullptr);

The last thing we need to do is create the actual context. For this we need some context attributes. These are created in a similar way to the pixel format attributes we created a little while ago. First we check for the extension:

if(!WGLEW_ARB_create_context)
{
    std::cout << "Create Context Extension Not Supported; Exiting..." << std::endl;
    exit(0);
}

Then we populate an array of attributes:

int context_attributes[] =
{
    WGL_CONTEXT_MAJOR_VERSION_ARB, 4,
    WGL_CONTEXT_MINOR_VERSION_ARB, 1,
    WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
    0 // End of attributes list
};

The first two are fairly obvious, these are the major/minor version of OpenGl that you want your context to create.
The last attribute (WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB) tells the context to completely disable all deprecated features up to the requested context. We then call:

gl_context = wglCreateContextAttribsARB(device_context, 0, context_attributes);
wglMakeCurrent(device_context, gl_context);
std::cout << "OpenGL: OpenGL version " << glGetString(GL_VERSION) << " created." << std::endl << std::endl;

The call to wglCreateContextAttribsARB replaces the call to wglCreateContext that the simple context used.
Then we call wglMakeCurrent in the same way as we did before, and the message being sent to the console should tell you which version of OpenGL the created context actually supports.
If that’s not enough to convince you its working then add:

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glClearColor(1.0f, 1.0f, 0.0f, 1.0f);
SwapBuffers(device_context);

Into the message loop (after the if()…else) and your window will show up yellow.

Tidying Up

So we have a working context.
But nobody wants to write all that code every time they start a new project so we should tidy this up a bit.
Normally I would just go straight to turning this into a class, but for the sake of clarity I’m going to just break it into local methods in the main.cpp file for now.
But even before that there are some things that can be done.
The first thing is that we can zero out the WNDCLASSEX and PIXELFORMATDESCRIPTOR structs before setting just the values we care about. This basically means that the entire struct is set to 0. So for WNDCLASSEX:

WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_OWNDC;
wcex.lpfnWndProc = DefWindowProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = instance;  // The instance we got above
wcex.hIcon = NULL;
wcex.hCursor = LoadCursor(NULL, IDC_ARROW); // Just use the arrow for now
wcex.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); // Set it to black
wcex.lpszMenuName = NULL;
wcex.lpszClassName = L"tutorial_1_window";  // The class name
wcex.hIconSm = NULL;

Becomes:

WNDCLASSEX wcex;
SecureZeroMemory(&wcex, sizeof(WNDCLASSEX));
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_OWNDC;
wcex.lpfnWndProc = DefWindowProc;
wcex.hInstance = instance;  // The instance we got above
wcex.hCursor = LoadCursor(NULL, IDC_ARROW); // Just use the arrow for now
wcex.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); // Set it to black
wcex.lpszClassName = L"tutorial_1_window";  // The class name

And for the PIXELFORMATDESCRIPTOR:

PIXELFORMATDESCRIPTOR pixel_format_desc =
{
    sizeof(PIXELFORMATDESCRIPTOR),
    1,
    PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER,
    PFD_TYPE_RGBA,
    32,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    24,
    8,
    0,
    PFD_MAIN_PLANE,
    0,
    0,
    0,
    0
};

Becomes:

PIXELFORMATDESCRIPTOR pixel_format_desc;
SecureZeroMemory(&pixel_format_desc, sizeof(PIXELFORMATDESCRIPTOR));
pixel_format_desc.nSize = sizeof(PIXELFORMATDESCRIPTOR);
pixel_format_desc.nVersion = 1;
pixel_format_desc.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
pixel_format_desc.iPixelType = PFD_TYPE_RGBA;
pixel_format_desc.cColorBits = 32;
pixel_format_desc.cDepthBits = 24;
pixel_format_desc.cStencilBits = 8;
pixel_format_desc.iLayerType = PFD_MAIN_PLANE;

So that’s a couple of basic things tidied up, I only included the more verbose versions initially so that the structs could be described in slightly more detail.
Now to split it into methods.
To me at least it seems that 3 things are happening:

  1. A window is created
  2. GLEW is initialized
  3. An OpenGL context is created

These seem like candidates for methods so let’s start with the first: Define a method with the signature

HWND createApplicationWindow();

Move the first section of code into the body of that method. Basically everything from:

HINSTANCE instance = GetModuleHandle(nullptr);

to, with th eonly change being that we return the call to CreateWindow(...) rather than assigning from it.

window_handle = CreateWindow(…)

So the method looks like:

HWND createApplicationWindow(
) {
    HINSTANCE instance = GetModuleHandle(nullptr);
    WNDCLASSEX wcex;
    SecureZeroMemory(&wcex, sizeof(WNDCLASSEX));
    wcex.cbSize = sizeof(WNDCLASSEX);
    wcex.style = CS_OWNDC;
    wcex.lpfnWndProc = DefWindowProc;
    wcex.hInstance = instance;  // The instance we got above
    wcex.hCursor = LoadCursor(NULL, IDC_ARROW); // Just use the arrow for now
    wcex.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); // Set it to black
    wcex.lpszClassName = L"tutorial_1_window";  // The class name
    RegisterClassEx(&wcex);

    return CreateWindow(
        L"tutorial_1_window",      // The class we created above
        L"Tutorial 1",             // The title of the window
        WS_BORDER | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX, // The style of the window
        200,                       // The x position of the top left
        200,                       // The y position of the window
        600,                       // The width of the window
        400,                       // The height of the window
        NULL,                      // The parent of the window
        NULL,                      // The menu for the window
        instance,                  // The module instance
        NULL                       // Not sure don't worry about it
    );
}

Then create another method with the signature

bool initializeGLEW(const HWND& win_handle, const HDC& device_context);

and populate the body of that method with the code related to setting up the simple context and initialising GLEW. It should look something like:

bool initializeGLEW(
    const HWND& win_handle,
    const HDC& device_context
) {
    PIXELFORMATDESCRIPTOR pixel_format_desc;
    SecureZeroMemory(&pixel_format_desc, sizeof(PIXELFORMATDESCRIPTOR));
    pixel_format_desc.nSize = sizeof(PIXELFORMATDESCRIPTOR);
    pixel_format_desc.nVersion = 1;
    pixel_format_desc.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
    pixel_format_desc.iPixelType = PFD_TYPE_RGBA;
    pixel_format_desc.cColorBits = 32;
    pixel_format_desc.cDepthBits = 24;
    pixel_format_desc.cStencilBits = 8;
    pixel_format_desc.iLayerType = PFD_MAIN_PLANE;

    int pixel_format = ChoosePixelFormat(device_context, &pixel_format_desc);
    SetPixelFormat(device_context, pixel_format, &pixel_format_desc);

    HGLRC  gl_context = wglCreateContext(device_context);
    wglMakeCurrent(device_context, gl_context);

    glewExperimental = TRUE;
    auto err = glewInit();
    if(err != GLEW_OK)
    {
        //Problem: glewInit failed, something is seriously wrong.
        std::cout << "glewInit failed, aborting." << std::endl;
        return false;
    }
    std::cout << "Using GLEW Version: " << glewGetString(GLEW_VERSION) << std::endl;
    std::cout << "OpenGL: OpenGL version " << glGetString(GL_VERSION) << " supported" << std::endl;
    wglMakeCurrent(nullptr, nullptr);
    wglDeleteContext(gl_context);
    return true;
}

note that we pass in the device context rather than creating it within thi smethod, also we return false rather than exiting on an error.

The final method should have the signature

bool createOpenGLContext(const HWND& win_handle, const HDC& device_context, HGLRC* gl_context);

and look something like:

bool createOpenGLContext(
    const HWND& win_handle,
    const HDC& device_context,
    HGLRC* gl_context
) {
    if(!WGLEW_ARB_pixel_format)
    {
        std::cout << "Pixel Format Extension Not Supported; Exiting..." << std::endl;
        return false;
    }
    const int pixel_format_attribute_list[] =
    {
        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 of attributes list
    };

    int improved_pixel_format;
    UINT num_formats;
    if(wglChoosePixelFormatARB(device_context,
                               pixel_format_attribute_list,
                               nullptr,                     // We don’t use this
                               1,                           // Just get the best match
                               &improved_pixel_format,
                               &num_formats) == 0)
    {
        std::cout << "Couldn't find valid pixel format" << std::endl;
        return false;
    }
    SetPixelFormat(device_context, improved_pixel_format, nullptr);

    if(!WGLEW_ARB_create_context)
    {
        std::cout << "Create Context Extension Not Supported; Exiting..." << std::endl;
        return false;
    }
    int context_attributes[] =
    {
        WGL_CONTEXT_MAJOR_VERSION_ARB, 4,
        WGL_CONTEXT_MINOR_VERSION_ARB, 1,
        WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
        0 // End of attributes list
    };
    *gl_context = wglCreateContextAttribsARB(device_context, 0, context_attributes);
    wglMakeCurrent(device_context, *gl_context);
    std::cout << "OpenGL: OpenGL version " << glGetString(GL_VERSION) << " created." << std::endl << std::endl;
    return true;
}

Again, failulres return false now rather than just exiting.

Then using these new methods main can be changed to look like:

int main(int argc, const char* argv[])
{
    HWND window_handle = createApplicationWindow();
    HDC device_context = GetDC(window_handle);

    if(!initializeGLEW(window_handle, device_context))
    {
        exit(0);  // GLEW failed to initialize.  Just exit now.
    }

    HGLRC gl_context;
    if(!createOpenGLContext(window_handle, device_context, &gl_context))
    {
        exit(0);
    }

    // Add nothing after this
    ShowWindow(window_handle, SW_SHOW);
    UpdateWindow(window_handle);
    MSG msg;
    BOOL message_return;
    bool run_loop = true;
    while(run_loop && (message_return = GetMessage(&msg, window_handle, 0, 0)) != 0)
    {
        if(message_return == -1)
        {
            run_loop = false;
        }
        else
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glClearColor(1.0f, 1.0f, 0.0f, 1.0f);
        SwapBuffers(device_context);
    }
    wglDeleteContext(gl_context);
    DestroyWindow(window_handle);  // Clean up after ourselves
    return 0;
}

To me that main function still looks a bit busy. Ideally I’d like it to have no Win32 calls in it at all. To do that we can create a new class that will take care of creating a window and attaching an OpenGL context. How you approach the design of a class, and what you define in its public API is a big topic, so I’m not going to cover it in detail here. The one thing I think is important is that you should define the API first and then create a class around that. Rather than just jump in writing code. So what would an OpenGL windowing class need? Obviously a name, let’s call it GLWindow. Create two new files GLWindow.h and GLWindow.cpp in your project

And add the following code:

GLWindow.h

#pragma once
class GLWindow
{
public:
    GLWindow();
    ~GLWindow();
};

GLWindow.cpp:

#include "stdafx.h"
#include "GLWindow.h"

GLWindow::GLWindow(
) {}

GLWindow::~GLWindow(
) {}

We’ll need various class members. So add

private:
    HWND m_window_handle;
    HDC m_device_context;
    HGLRC m_gl_context;

to the header. We can also use the three methods we defined earlier as a basis for the class. They will be slightly different here so we’ll declare them at the class level as:

private: // Helper Methods
    void createApplicationWindow();
    void initializeGlew();
    void createOpenGLContext();

Notice they don’t have parameters now, they will just be acting on the class members, also they all return void. You could have them return bool to check for success as we did earlier, however I don’t particularly like that pattern. I’m not going to cover the pros and cons of things like exceptions or returning failure codes, that’s a topic for a different time so we’re going to make the rather naïve assumption that all our stuff works. Define them in the .cpp file as:

void GLWindow::createApplicationWindow(
) {
    HINSTANCE instance = GetModuleHandle(nullptr);
    WNDCLASSEX wcex;
    SecureZeroMemory(&wcex, sizeof(WNDCLASSEX));
    wcex.cbSize = sizeof(WNDCLASSEX);
    wcex.style = CS_OWNDC;
    wcex.lpfnWndProc = DefWindowProc;
    wcex.hInstance = instance;
    wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
    wcex.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
    wcex.lpszClassName = L"tutorial_1_window";

    RegisterClassEx(&wcex);
    m_window_handle = CreateWindow(
        L"tutorial_1_window",
        L"Tutorial 1",
        WS_BORDER | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,
        200,
        200,
        600,
        400,
        NULL,
        NULL,
        instance,
        NULL);
    m_device_context = GetDC(m_window_handle);
}
void GLWindow::initializeGlew(
) {
    // Initialize basic OpenGl
    // We need a PIXELFORMATDESCRIPTOR
    PIXELFORMATDESCRIPTOR pixel_format_desc;
    SecureZeroMemory(&pixel_format_desc, sizeof(PIXELFORMATDESCRIPTOR));
    pixel_format_desc.nSize = sizeof(PIXELFORMATDESCRIPTOR);
    pixel_format_desc.nVersion = 1;
    pixel_format_desc.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
    pixel_format_desc.iPixelType = PFD_TYPE_RGBA;
    pixel_format_desc.cColorBits = 32;
    pixel_format_desc.cDepthBits = 24;
    pixel_format_desc.cStencilBits = 8;
    pixel_format_desc.iLayerType = PFD_MAIN_PLANE;

    int pixel_format = ChoosePixelFormat(m_device_context, &pixel_format_desc);
    SetPixelFormat(m_device_context, pixel_format, &pixel_format_desc);
    m_gl_context = wglCreateContext(m_device_context);
    wglMakeCurrent(m_device_context, m_gl_context);

    glewExperimental = TRUE;
    auto err = glewInit();
    if(err != GLEW_OK)
    {
        //Problem: glewInit failed, something is seriously wrong.
        std::cout << "glewInit failed." << std::endl;
    }

    std::cout << "Using GLEW Version: " << glewGetString(GLEW_VERSION) << std::endl;
    std::cout << "OpenGL: OpenGL version " << glGetString(GL_VERSION) << " supported" << std::endl;
    wglMakeCurrent(nullptr, nullptr);
    wglDeleteContext(m_gl_context);
}
void GLWindow::createOpenGLContext(
) {
    // Pixel Format
    if(!WGLEW_ARB_pixel_format || !WGLEW_ARB_create_context)
    {
        std::cout << "Extensions Not Supported Exiting..." << std::endl;
    }

    const int pixel_format_attribute_list[] =
    {
        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 of attributes list
    };

    int improved_pixel_format;
    UINT num_formats;
    if(wglChoosePixelFormatARB(m_device_context,
                               pixel_format_attribute_list,
                               nullptr,                     // We don’t use this
                               1,                           // Just get the best match
                               &improved_pixel_format,
                               &num_formats) == 0)
    {
        std::cout << "Couldn't find valid pixel format" << std::endl;
    }
    SetPixelFormat(m_device_context, improved_pixel_format, nullptr);

    int context_attributes[] =
    {
        WGL_CONTEXT_MAJOR_VERSION_ARB, 4,
        WGL_CONTEXT_MINOR_VERSION_ARB, 1,
        WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
        0 // End of attributes list
    };
    m_gl_context = wglCreateContextAttribsARB(m_device_context, 0, context_attributes);
    wglMakeCurrent(m_device_context, m_gl_context);
    std::cout << "OpenGL: OpenGL version " << glGetString(GL_VERSION) << " created." << std::endl << std::endl;
}

These are practically identical to the way they were in the main.cpp file, so I’m not going to go into any further detail on them. Add calls to these into the constructor:

GLWindow::GLWindow(
) {
    createApplicationWindow();
    initializeGlew();
    createOpenGLContext();
}

And also move the calls to ShowWindow(… and UpdateWindow(…) into the constructor:

GLWindow::GLWindow(
) {
    createApplicationWindow();
    initializeGlew();
    createOpenGLContext();

    ShowWindow(m_window_handle, SW_SHOW);
    UpdateWindow(m_window_handle);
}

Move the clean-up code into the destructor:

GLWindow::~GLWindow(
) {
    wglDeleteContext(m_gl_context);
    DestroyWindow(m_window_handle);
}

The next thing is we need to move the message loop into the class. At the moment we will keep this very simple. We’re not interacting with anything or detecting user input yet so this doesn’t have to be very clever. Just add a public method:

bool run();

and move the whole message loop into it as below, this is slightly different than we had before, and this is due to the fact that the message loop will run separately from the main update loop:

bool GLWindow::run()
{
    MSG msg;
    if(!IsWindow(m_window_handle))
    {
        return false;
    }
    while(PeekMessage(&msg, m_window_handle, 0, 0, PM_REMOVE))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return true;
}

Finally we need to provide a method for swapping the back buffer. Define a method with the signature:

void swapBuffers();

And define it as:

void GLWindow::swapBuffers()
{
    SwapBuffers(m_device_context);
}

The main function can now be defined as:

int main(int argc, const char* argv[])
{
    GLWindow test_window;
    while(test_window.run())
    {
        // App running
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glClearColor(1.0f, 1.0f, 0.0f, 1.0f);
        // Your OpenGLCode here...
        test_window.swapBuffers();
    }
    return 0;
}

If we try and build this now the build will fail. All we need to do is move the #includes from the main.cpp file into the GLWindow.h file And add an include for GLWindow.h into main.cpp

Further Work

Obviously there is much more that could be done here, some ideas to get started:

  1. Parameterize the GLWindow class so the version of OpenGL requested can be changed.
  2. Add proper error handling.
  3. Create a fallback option if the extensions don’t load, that will allow the use of older styles of OpenGL.