Loading OpenGL without GLEW
Loading OpenGL without GLEW
This article is a result of my experiences and experiments with using OpenGL for
[Papaya]. My first few weeks learning OpenGL were challenging, because it was
very different than most of the APIs I had interacted with up to that point. I'm
taking [Julia Evans' advice], and hence writing this blog post because it
would certainly have helped me a year ago. Intermediate or experienced
programmers may want to skip this explanation entirely.
**The complete OpenGL loader code described in this post is located [here].**
Note that the code may change in the future, so the link points to the file as
it existed at the time when this post was written. This code was written to
cover Papaya's usage of OpenGL. You may need to add further functions/constants
manually depending on your program's usage.
What follows, is hopefully a beginner-friendly explanation.
What is OpenGL loading?
OpenGL is an API specification. It is _not_ a library. This means that the
actual implementation behind the API varies based on your GPU hardware, your
operating system, and your installed graphics driver.
The OpenGL specification has a lot of different functions defined in it, and the
OpenGL specification gets updated periodically. The graphics driver on your
machine may not (and probably does not) support all of these functions. The
subset of the spec that is supported will depend on your GPU hardware
capabilities and the GPU vendor's support for any given API.
This is the reason that all OpenGL functions aren't statically declared in a
header file. Furthermore, static linkage to a library is impossible because
target machines for your application will have a wildly varying set of OpenGL
implementations. On Windows machines, the OpenGL implementation will be in the
form a DLL. On 64-bit Windows, the 64-bit dll will be located in
`C:\Windows\system32\opengl32.dll` (confusing nomenclature ftw). This DLL is a
component of the graphics driver, and is shipped with it.
To recap, most OpenGL functions are not declared in any standardized header
file, and they are not linked statically. This is why OpenGL functions can't
just be called out of the box, and need to be declared and loaded explicitly.
What is GLEW?
[GLEW] (OpenGL Extension Wrangler) is a cross-platform library
that declares and loads OpenGL functions for you. It also has handy run-time
checks to see whether a given machine supports a given OpenGL profile (a
profile is a guarantee that a given configuration supports a certain set of
GLEW is very handy, especially if you are new to OpenGL. It does most of the
heavy-lifting for you, and you remain free to call any valid OpenGL function
supported on your system.
Most StackOverflow questions regarding OpenGL loading, advise the original
questioner to simply stop doing custom loading and move to GLEW. Even the
official OpenGL wiki [strongly advises] you to use an OpenGL loading library.
While automatic loading libraries are handy, they do have some significant
disadvantages. I will restrict my critique to GLEW in this particular article,
but the same disadvantages generally do apply to other kinds of OpenGL loaders
Why you may be better off without GLEW
You can use GLEW in two ways:
1. Dynamically linking: Papaya is an open-source application. Ease of building is very important to me. If an application is easy to build on someone else's machine, they are much more likely to want to work with it. Dynamic linkage means that I have to either set up a separate build process for GLEW, or I need to ship the platform and architecture-specific dynamic libraries with my source. Either way, seems very aesthetically displeasing to me.
2. Shipping the source: This is what I did with Papaya initially. I shipped the GLEW source and statically compiled it with the application. This keeps the build process simple and I don't need to ship platform-specific binaries with my application code. The big downside to this is that the GLEW source code is big.
If you haven't done so already, I recommend you watch Fabian Giesen's interviews
([Part 1], [Part 2]). In them, Fabian discusses the idea that code
complexity is a cost too, and is also an axis on which a program should be
optimized. This idea has grown on me over time, and I now try to optimize code
along its complexity axis, in addition to the performance axis. Code that you
will never use in any library is basically dead code, and does increase code
Here's a comparison of build times and the lines of code before and after I
removed GLEW from Papaya.
| Metric | GLEW | No GLEW |
|Full rebuild time | 6.9 sec | 5.5 sec |
|Lines of code (cloc) | 62820 loc | 25427 loc |
_Build times were measured using the Linux `time` command on a laptop with an
SSD, and are an average of 5 passes each. Your milage may vary._
I ran `cloc` on just the GLEW folder, and it totaled 37,393 lines of code. That
is ridiculously large. To put things into perspective, the rest of my
application - platform code, image libraries, UI libraries, everything else -
totaled 25,427 lines of code. _Just the GLEW code was larger than the rest of my
This primarily happens because GLEW has code to handle _all_ OpenGL function
APIs, where as I use a very small percentage of them.
My replacement code for GLEW is only ~180 lines long, and works on Windows and
Linux. Mac support isn't present at the moment, but should be very trivial to
Build times have also reduced by over a second.
How this loader works
In this section, I will describe only the interesting excerpts of the code. To
reiterate, **you can view the full source code [here].**
This code is based on Fabian Giesen's [Bink GL extension loader]. I have
added platform-specific code, and packaged it as a single-header file.
#define GLDECL // Empty define
#define PAPAYA_GL_LIST_WIN32 // Empty define
#endif // __linux__
#define GLDECL WINAPI
#define GL_ARRAY_BUFFER 0x8892
#define GL_ARRAY_BUFFER_BINDING 0x8894
typedef char GLchar;
typedef ptrdiff_t GLintptr;
typedef ptrdiff_t GLsizeiptr;
#define PAPAYA_GL_LIST_WIN32 \
/* ret, name, params */ \
GLE(void, BlendEquation, GLenum mode) \
GLE(void, ActiveTexture, GLenum texture) \
/* end */
#endif // _WIN32
In the platform-specific header, we include the files needed for getting loading
dynamic library binaries - `dlfcn.h` on POSIX systems, and `windows.h` on
Windows. We also define the calling convention prefix `GLDECL` here, which is
primarily needed for the loader to work correctly on 32-bit Windows.
We include the header file `GL/gl.h` in this loader, but this file is usually
different on Windows and Linux. The Windows SDK ships with an older version
(OpenGL 1.1) of the file, and hence does not contain all of the typedefs,
constants and function declarations that are present in the Linux version of the
same file. These need to be added manually to the Windows-specific part of our
header. These constants and functions can be found in the [glext.h file]. We
can choose to just include that entire file as well, but I have currently
elected not to go that route.
#define PAPAYA_GL_LIST \
/* ret, name, params */ \
GLE(void, AttachShader, GLuint program, GLuint shader) \
GLE(void, BindBuffer, GLenum target, GLuint buffer) \
/* end */
#define GLE(ret, name, ...) \
typedef ret GLDECL name##proc(__VA_ARGS__); \
extern name##proc * gl##name;
This is the bulk of the loader. We declare all the GL functions we want to use
in the PAPAYA_GL_LIST macro. These function prototypes can be found in the
OpenGL registry, or on a website like [docs.gl]. These are listed in the
format `GLE(return type, function name, params)`. We don't include the "gl"
prefix of the function name, purely for cosmetic reasons.
Note the PAPAYA_GL_LIST and GLE() macros. I took this directly from the Bink GL
loader, and it is a very nifty pattern for defining macro iterators.
Here, this iterator macro is used to typedef function pointers, and then to
declare extern function pointers. These same function pointers are later
declared in the implementation section of our file. After we include this header
file, we can call OpenGL functions such as `glAttachShader(...)` without getting
Finally, the [gl_lite_init()] function contains the platform-specific code
to load the OpenGL dynamic library, and then get the function pointers from
them. Both - POSIX and Windows - follow the same basic steps:
1. The library is loaded, and a handle is obtained. On windows, the OpenGL driver interface is located in the `opengl32.dll`, and in Linux, it is in the `libGL.so` (shared object) file. The library loading function returns an opaque handle.
2. This library handle is used to load a given function, based on the function name. On Windows, there is an additional level of indirection to function loading, and has to be done via the wglGetProcAddress function.
GLEW is handy, but does have its disadvantages. Writing your own GL loader isn't
rocket science, and will result in faster compilation, easier builds, and a
smaller code base. This isn't a library as much as a code snippet, and you will
probably need to customize the code to fit your needs. You can view the full
code described in this article [here]. The file is public domain, so feel
free to use it wherever.
If you have comments, suggestions, corrections or questions, feel free to get in
touch with me on Twitter or email (links in the footer).
Email / Twitter / GitHub / CV