OpenGL from scratch

Do you really want to do this?

As mentioned in previous part in order to access modern OpenGL functionality we need to query extensions with wglGetProcAddress. I should mention that doing it manually is a very tedious process, especially if you're targeting multiple platforms. If you want to be clever you can probably write a script to do it automatically (glad does that). Also there are libraries like GLEW. You should at least consider saving yourself from the work by using GLEW, Glee or some other library.

For the sake of showing how to query the extensions we will do it ourselves this time. I will only show how to do it in this tutorial and for the later tutorials I will assume you query all the necessary functions yourself or use a library that does it for you. In addition to querying pointers to the API with wglGetProcAddress, we also need the definitions for the constants. You can find these constants and also the function declarations in glcorearb.h. You can just include that if you don't want to define them yourself, but for this part of the tutorial I will assume we will only use windows.h and GL/GL.h.

Let's do it

For the tutorial series I'd want to keep things in single file as much as possible. However I plan to use GLEW for the later tutorials, so separating the extension code to separate file makes more sense in this case.
So let's make a new header file GLExt.h. First thing you should do is make sure the header has an include guard.
Next let's add the headers we need:

#define WIN32_LEAN_AND_MEAN #include <windows.h> #include <gl/GL.h> #include <stddef.h>

Defining WIN32_LEAN_AND_MEAN before including windows.h makes it more lightweight, sounds good for us.
Next I've selected some OpenGL definitions we will need.

#define GL_MAJOR_VERSION 0x821B #define GL_MINOR_VERSION 0x821C #define GL_ARRAY_BUFFER 0x8892 #define GL_ELEMENT_ARRAY_BUFFER 0x8893 #define GL_STATIC_DRAW 0x88E4 #define GL_STATIC_READ 0x88E5 #define GL_STATIC_COPY 0x88E6 #define GL_DYNAMIC_DRAW 0x88E8 #define GL_DYNAMIC_READ 0x88E9 #define GL_DYNAMIC_COPY 0x88EA #define GL_FRAGMENT_SHADER 0x8B30 #define GL_VERTEX_SHADER 0x8B31 #define GL_COMPILE_STATUS 0x8B81 #define GL_LINK_STATUS 0x8B82 #ifndef GLAPIENTRY #ifdef APIENTRY #define GLAPIENTRY APIENTRY #else #define GLAPIENTRY __stdcall #endif #endif #define GL_FUN_EXPORT extern typedef char GLchar; typedef ptrdiff_t GLsizeiptr;

GLAPIENTRY will just be an 'alias' for __stdcall. We need it later for the function pointer definitions. Basically it's an attribute for the compiler telling that the functions should be called using the standard calling convention. GL_FUN_EXPORT will be an alias for extern as you can see. It is needed to share the function pointers between compilation units. Not much to say about the rest, we will need them.

As mentioned we will only be setting it up for the next tutorial (drawing a triangle) and here are the functions we'll need. (don't paste this in the code...)

glGenBuffers glDeleteBuffers glBindBuffer glBufferData glDrawArrays glEnableVertexAttribArray glVertexAttribPointer glCreateShader glShaderSource glCompileShader glGetShaderiv glGetShaderInfoLog glAttachShader glGetProgramiv glGetProgramInfoLog glDetachShader glCreateProgram glUseProgram

To use the functions we need type definitions for them. They are from corearb.h and I've changed them slightly.

typedef void (GLAPIENTRY *PFNGLGENBUFFERSPROC) (GLsizei n, GLuint *buffers); typedef void (GLAPIENTRY *PFNGLDELETEBUFFERSPROC) (GLsizei n, const GLuint *buffers); typedef void (GLAPIENTRY *PFNGLBINDBUFFERPROC) (GLenum target, GLuint buffer); typedef void (GLAPIENTRY *PFNGLBUFFERDATAPROC) (GLenum target, GLsizeiptr size, const void *data, GLenum usage); typedef void (GLAPIENTRY *PFNGLDRAWARRAYSPROC) (GLenum mode, GLint first, GLsizei count); typedef void (GLAPIENTRY *PFNGLENABLEVERTEXATTRIBARRAYPROC) (GLuint index); typedef GLuint(GLAPIENTRY *PFNGLCREATESHADERPROC) (GLenum type); typedef void (GLAPIENTRY *PFNGLSHADERSOURCEPROC) (GLuint shader, GLsizei count, const GLchar *const*string, const GLint *length); typedef void (GLAPIENTRY *PFNGLCOMPILESHADERPROC) (GLuint shader); typedef void (GLAPIENTRY *PFNGLGETSHADERIVPROC) (GLuint shader, GLenum pname, GLint *params); typedef void (GLAPIENTRY *PFNGLGETSHADERINFOLOGPROC) (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog); typedef void (GLAPIENTRY *PFNGLATTACHSHADERPROC) (GLuint program, GLuint shader); typedef void (GLAPIENTRY *PFNGLGETPROGRAMIVPROC) (GLuint program, GLenum pname, GLint *params); typedef void (GLAPIENTRY *PFNGLGETPROGRAMINFOLOGPROC) (GLuint program, GLsizei bufSize, GLsizei *length, GLchar *infoLog); typedef void (GLAPIENTRY *PFNGLDETACHSHADERPROC) (GLuint program, GLuint shader); typedef GLuint(GLAPIENTRY *PFNGLCREATEPROGRAMPROC) (void); typedef void (GLAPIENTRY *PFNGLLINKPROGRAMPROC) (GLuint program); typedef void (GLAPIENTRY *PFNGLVERTEXATTRIBPOINTERPROC) (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer); typedef void (GLAPIENTRY *PFNGLUSEPROGRAMPROC) (GLuint program); typedef void (GLAPIENTRY *PFNGLBINDVERTEXARRAYPROC) (GLuint array);

These were the type definitions, next we need variable definitions and declarations.
The following definitions go into the header.

GL_FUN_EXPORT PFNGLGENBUFFERSPROC glGenBuffers; GL_FUN_EXPORT PFNGLDELETEBUFFERSPROC glDeleteBuffers; GL_FUN_EXPORT PFNGLBINDBUFFERPROC glBindBuffer; GL_FUN_EXPORT PFNGLBUFFERDATAPROC glBufferData; GL_FUN_EXPORT PFNGLENABLEVERTEXATTRIBARRAYPROC glEnableVertexAttribArray; GL_FUN_EXPORT PFNGLCREATESHADERPROC glCreateShader; GL_FUN_EXPORT PFNGLSHADERSOURCEPROC glShaderSource; GL_FUN_EXPORT PFNGLCOMPILESHADERPROC glCompileShader; GL_FUN_EXPORT PFNGLGETSHADERIVPROC glGetShaderiv; GL_FUN_EXPORT PFNGLGETSHADERINFOLOGPROC glGetShaderInfoLog; GL_FUN_EXPORT PFNGLATTACHSHADERPROC glAttachShader; GL_FUN_EXPORT PFNGLGETPROGRAMIVPROC glGetProgramiv; GL_FUN_EXPORT PFNGLGETPROGRAMINFOLOGPROC glGetProgramInfoLog; GL_FUN_EXPORT PFNGLDETACHSHADERPROC glDetachShader; GL_FUN_EXPORT PFNGLCREATEPROGRAMPROC glCreateProgram; GL_FUN_EXPORT PFNGLLINKPROGRAMPROC glLinkProgram; GL_FUN_EXPORT PFNGLVERTEXATTRIBPOINTERPROC glVertexAttribPointer; GL_FUN_EXPORT PFNGLUSEPROGRAMPROC glUseProgram; GL_FUN_EXPORT PFNGLBINDVERTEXARRAYPROC glBindVertexArray;

Now we also need the declarations that would go inside a '.cpp' file. We'll initialize them to NULL. Instead of initializing them to NULL you could point them to some stub function by default.
I'm a fan of single header libraries so I'll place the implementation code also in the .h file and define it out with #ifdef macro.
If this seems confusing then you can look it up in the final code later.

#ifdef GL_EXT_IMPLEMENTATION PFNGLGENBUFFERSPROC glGenBuffers = NULL; PFNGLDELETEBUFFERSPROC glDeleteBuffers = NULL; PFNGLBINDBUFFERPROC glBindBuffer = NULL; PFNGLBUFFERDATAPROC glBufferData = NULL; PFNGLENABLEVERTEXATTRIBARRAYPROC glEnableVertexAttribArray = NULL; PFNGLCREATESHADERPROC glCreateShader = NULL; PFNGLSHADERSOURCEPROC glShaderSource = NULL; PFNGLCOMPILESHADERPROC glCompileShader = NULL; PFNGLGETSHADERIVPROC glGetShaderiv = NULL; PFNGLGETSHADERINFOLOGPROC glGetShaderInfoLog = NULL; PFNGLATTACHSHADERPROC glAttachShader = NULL; PFNGLGETPROGRAMIVPROC glGetProgramiv = NULL; PFNGLGETPROGRAMINFOLOGPROC glGetProgramInfoLog = NULL; PFNGLDETACHSHADERPROC glDetachShader = NULL; PFNGLCREATEPROGRAMPROC glCreateProgram = NULL; PFNGLLINKPROGRAMPROC glLinkProgram = NULL; PFNGLVERTEXATTRIBPOINTERPROC glVertexAttribPointer = NULL; PFNGLUSEPROGRAMPROC glUseProgram = NULL; PFNGLBINDVERTEXARRAYPROC glBindVertexArray = NULL; #endif

For the header macro trick to work you would have to do #define GL_EXT_IMPLEMENTATION once somewhere in your code base before #include "GLExt.h"
Now we have everything ready to start querying the extensions. We should make an initialization function that fills out the function pointers. To keep things simple I've also added the version requirements to that function. Add the following function definition somewhere before the #ifdef GL_EXT_IMPLEMENTATION

bool InitOpenGLExt(int min_major_ver, int min_minor_ver);

The function returns false on failure so that we can display some kind of error in the initialization code. Now let's put the declaration inside the #ifdef block

bool InitOpenGLExt(int min_major_ver, int min_minor_ver) { int major_ver, minor_ver; glGetIntegerv(GL_MAJOR_VERSION, &major_ver); glGetIntegerv(GL_MINOR_VERSION, &minor_ver); if (major_ver == GL_INVALID_ENUM || minor_ver == GL_INVALID_ENUM) { return false; } if (major_ver < min_major_ver) { return false; } if (major_ver == min_major_ver && minor_ver < min_minor_ver) { return false; } bool anyErrors = false; anyErrors = (glCreateShader = (PFNGLCREATESHADERPROC)wglGetProcAddress("glCreateShader")) == NULL || anyErrors; anyErrors = (glGenBuffers = (PFNGLGENBUFFERSPROC)wglGetProcAddress("glGenBuffers")) == NULL || anyErrors; anyErrors = (glDeleteBuffers = (PFNGLDELETEBUFFERSPROC)wglGetProcAddress("glDeleteBuffers")) == NULL || anyErrors; anyErrors = (glBindBuffer = (PFNGLBINDBUFFERPROC)wglGetProcAddress("glBindBuffer")) == NULL || anyErrors; anyErrors = (glBufferData = (PFNGLBUFFERDATAPROC)wglGetProcAddress("glBufferData")) == NULL || anyErrors; anyErrors = (glEnableVertexAttribArray = (PFNGLENABLEVERTEXATTRIBARRAYPROC)wglGetProcAddress("glEnableVertexAttribArray")) == NULL || anyErrors; anyErrors = (glCreateShader = (PFNGLCREATESHADERPROC)wglGetProcAddress("glCreateShader")) == NULL || anyErrors; anyErrors = (glShaderSource = (PFNGLSHADERSOURCEPROC)wglGetProcAddress("glShaderSource")) == NULL || anyErrors; anyErrors = (glCompileShader = (PFNGLCOMPILESHADERPROC)wglGetProcAddress("glCompileShader")) == NULL || anyErrors; anyErrors = (glGetShaderiv = (PFNGLGETSHADERIVPROC)wglGetProcAddress("glGetShaderiv")) == NULL || anyErrors; anyErrors = (glGetShaderInfoLog = (PFNGLGETSHADERINFOLOGPROC)wglGetProcAddress("glGetShaderInfoLog")) == NULL || anyErrors; anyErrors = (glAttachShader = (PFNGLATTACHSHADERPROC)wglGetProcAddress("glAttachShader")) == NULL || anyErrors; anyErrors = (glGetProgramiv = (PFNGLGETPROGRAMIVPROC)wglGetProcAddress("glGetProgramiv")) == NULL || anyErrors; anyErrors = (glGetProgramInfoLog = (PFNGLGETPROGRAMINFOLOGPROC)wglGetProcAddress("glGetProgramInfoLog")) == NULL || anyErrors; anyErrors = (glDetachShader = (PFNGLDETACHSHADERPROC)wglGetProcAddress("glDetachShader")) == NULL || anyErrors; anyErrors = (glCreateProgram = (PFNGLCREATEPROGRAMPROC)wglGetProcAddress("glCreateProgram")) == NULL || anyErrors; anyErrors = (glLinkProgram = (PFNGLLINKPROGRAMPROC)wglGetProcAddress("glLinkProgram")) == NULL || anyErrors; anyErrors = (glVertexAttribPointer = (PFNGLVERTEXATTRIBPOINTERPROC)wglGetProcAddress("glVertexAttribPointer")) == NULL || anyErrors; anyErrors = (glUseProgram = (PFNGLUSEPROGRAMPROC)wglGetProcAddress("glUseProgram")) == NULL || anyErrors; anyErrors = (glBindVertexArray = (PFNGLBINDVERTEXARRAYPROC)wglGetProcAddress("glBindVertexArray")) == NULL || anyErrors; return !anyErrors; }

First we try to get the major and minor version numbers of OpenGL. A tiny issue is that glGetIntegerv(GL_MAJOR_VERSION/GL_MINOR_VERSION) is only valid in OpenGL 3.0+. We don't really have to care about this because we will use modern OpenGL, so we just return with failure if the function fails. Next we check if the present OpenGL version is high enough.

As the last thing we query the function pointers with wglGetProcAddress. Note that if any of them fail the anyErrors variable will be true. With that the GLExt.h header is done.

Let's go back to the main file. Remove #include <GL/GL.h> from the main source file and add

#define GL_EXT_IMPLEMENTATION #include "GLExt.h"

Add the following after creating the OpenGL context.

if (!InitOpenGLExt(4, 0)) { MessageBoxA(hWnd, "Getting OpenGL extensions failed\n OpenGL 4.0+ is required", "Error", MB_OK | MB_ICONERROR); return -1; }

This code requires OpenGL 4.0, but we won't be using anything higher than 3.0 for a while. You can replace it whatever version you actually need.
That's pretty much all there is to querying the extensions. Unfortunately we made no visual progress in this part. If the code compiles and runs you should be good to go for the next part.
One additional thing you might want to do is wrap all your OpenGL functions into a macro that checks for an error after the function call (if it's a debug build). That way you'll immediately know which function call caused an error.

Final code

Next part: Drawing a triangle