Window Creation

Run these commands to setup a base project:

git clone tutorial
cd tutorial

This should open up a window.

Note: you need to open the Visual Studio Developer Command Prompt or run the vcvarsall.bat batch file to setup the environment before you can use the Microsoft command line build tools. Check my Win32 - Window Creation tutorial for a full explanation.


We need to load pointers to OpenGL functions. I'm going to use the Glad OpenGL loader to do that.

  • Download the library from
  • Select the gl API to the OpenGL version you want to support. I use the version 4.5 in this tutorial.
  • Click Generate at the bottom.
  • Click the file to download it.
  • Create a directory called libs/glad in the project directory and extract the package inside it.

You can retrieve and extract the package in the command line if you have CURL and tar available:

mkdir libs
cd libs
mkdir glad
cd glad
tar -zxf

You should now have this kind of directory structure:

        │   ├───glad
        │   └───KHR

Edit the main.c file and include the Glad loader library:

#include <windows.h>
#include <glad\glad.h> // HERE

Edit the build.bat script.

  • Add libs\glad\src\glad.c to it.
  • Add /Ilibs\glad\include to it.
  • Add opengl32.lib to it:
@echo off
cl main.c libs\glad\src\glad.c /Ilibs\glad\include /Fegame.exe /Zi /D "UNICODE" /D "_UNICODE" /nologo user32.lib gdi32.lib opengl32.lib 

Context Creation

Add the following variables

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


HDC dc;

// END

Each window has a device context (dc) object attached to it that we can use to store a pixel format. This describes the properties that the OpenGL context should have.

rc stores the handle for the OpenGL rendering context. The rendering context links OpenGL to the Windows windowing systems.

To get a pixel format, we first fill out a struct that describes what features we want. Then we use a function called ChoosePixelFormat() that returns a pixel format that matches those features the best.

The PIXELFORMATDESCRIPTOR struct has a lot of options. I'm going to use the example from this address:

  • Add the following code to the WM_CREATE case in the WindowProc function.
        // START

        case WM_CREATE:
            PIXELFORMATDESCRIPTOR pfd = { 
                sizeof(PIXELFORMATDESCRIPTOR),  //  size of this pfd  
                1,                     // version number  
                PFD_DRAW_TO_WINDOW |   // support window  
                    PFD_SUPPORT_OPENGL |  // support OpenGL  
                    PFD_DOUBLEBUFFER,      // double buffered  
                PFD_TYPE_RGBA,         // RGBA type  
                24,                    // 24-bit color depth  
                0, 0, 0, 0, 0, 0,      // color bits ignored  
                0,                     // no alpha buffer  
                0,                     // shift bit ignored  
                0,                     // no accumulation buffer  
                0, 0, 0, 0,            // accum bits ignored  
                32,                    // 32-bit z-buffer      
                0,                     // no stencil buffer  
                0,                     // no auxiliary buffer  
                PFD_MAIN_PLANE,        // main layer  
                0,                     // reserved  
                0, 0, 0                // layer masks ignored  

            dc = GetDC(hwnd);

            int pf = ChoosePixelFormat(dc, &pfd); 
            SetPixelFormat(dc, pf, &pfd);

            rc = wglCreateContext(dc);
            wglMakeCurrent(dc, rc);


            glViewport(0, 0, 640, 480);

                        "OPENGL VERSION",0);
            return 0;
        } break;

        // END
  • The WindowProc function receives the WM_CREATE message when we call the CreateWindow function.
  • GetDC(hwnd) returns a handle to a device context.
  • The ChoosePixelFormat function uses our PIXELFORMATDESCRIPTOR to get a suitable pixel format.
  • The SetPixelFormat sets the device context pixel format.
  • wglCreateContext creates an OpenGL rendering context.
  • wglMakeCurrent makes our rendering context current.
  • gladLoadGL() loads the pointers to OpenGL functions. Call it before you try to call any modern OpenGL function.
  • The glViewport() function sets the size of the rendering window. We match this to the window size.
  • glGetString returns a string that tells us the OpenGL version.

Release the device context and delete the OpenGL context in the WM_DESTROY case:

    case WM_DESTROY:
            // START

            ReleaseDC(hwnd, dc);

            // END

            return 0;
        } break;

Add the following lines to the game loop:

            if(msg.message == WM_QUIT)
                running = 0;


        // START

        glClearColor(1.0f, 0.0f, 0.0f, 1.0f);

        // END

At the start of each frame, we clear the screen.

  • The glClearColor() function specifies the clear color.
  • The glClear() functions clears the screen's color buffer. The GL_COLOR_BUFFER_BIT argument specifies the buffer we want to clear. In this case we are only interested in clearing the color buffer.
  • We don't render directly to the screen. Instead we render to a back buffer that is swapped to the front buffer when all rendering is done.

Run build.bat && game.exe. You should see a message box stating the OpenGL version and a window with a red client area.