libepoxy: OpenGL function pointer dispatch without the boilerplate
Epoxy is a library for handling OpenGL function pointer management for you
At a glance
- What is it?
- libepoxy is a C library that resolves OpenGL and OpenGL ES function pointers at runtime through dlopen, dlsym, glXGetProcAddress and eglGetProcAddress, so application code calls plain names like glCompileShader. It also knows the aliases between extension and core entry points, and it exposes helpers for querying the GL version and available extensions.
- Who is it for?
- libepoxy suits a project that wants one dispatch layer across desktop OpenGL, OpenGL ES, EGL, GLX and WGL without maintaining a generated extension parser of its own, since the alias handling alone removes a class of portability bugs. It does not suit a codebase that wants zero runtime lookup cost on the hot path, because the README is explicit that the query helpers are not fast.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Activity is slowing. The repository last received commits 6 months ago.
- What is it written in?
- Mainly C, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 25, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What epoxy hides from the application
Epoxy is a library for handling OpenGL function pointer management. It hides the complexity of dlopen(), dlsym(), glXGetProcAddress(), eglGetProcAddress() and the rest from the app developer, with very little knowledge needed on their part. They get to read the GL specs and write code using undecorated function names like glCompileShader().
That is the whole value proposition, and it is a real one. The traditional way to write portable OpenGL is to define function pointer typedefs, load them with a loader, and guard every call with a null check, which is a large amount of code for a project that only wants to draw a triangle.
The feature list spells out the coverage. GL functions are initialised automatically as they are used. There is GL 4.6 core and compatibility context support and GLES 1, 2 and 3 context support. EGL, GLX and WGL are all supported. The library knows about function aliases, so glBufferData() can be used with GL_ARB_vertex_buffer_object implementations along with GL 1.5 and later ones. And it can be mixed with non-epoxy GL usage, which matters when you are porting an existing codebase one file at a time rather than rewriting the renderer.
The last point about aliasing is the subtle one. The same function is often exported by several extensions and by core versions, and a caller should not have to know which provider to ask for. Epoxy picks the right one for you.
The README also keeps an honest warning in view: check for your extensions or versions being present before you use them, just like before, though epoxy will tell you what you forgot to check for instead of just segfaulting.
Building with Meson and Ninja
Epoxy builds with Meson, and the sequence is the standard out-of-tree one.
mkdir _build && cd _build
meson
ninja
sudo ninja installThe platform dependencies are short. On Debian the README lists meson and libegl1-mesa-dev. On macOS, using MacPorts, it lists pkgconfig and meson. The test suite has extra dependencies that depend on the platform, namely X11, EGL and a running X server, so the test run is the part that is not uniform across machines.
The repository layout reflects what the library has grown into. There is an include/ directory holding the public headers, src/ for the implementation, test/ for the test suite, doc/ for the documentation, and a registry/ directory, which is where the generated function and extension tables live. There is also a cross/ directory at the top level and a meson.build plus meson_options.txt at the root, so the build supports cross-compilation as a first-class option rather than an afterthought.
The CI badges in the README cover Ubuntu, macOS, an MSVC build and an MSYS2 build, so the four main desktop toolchains are built on every change.
Switching an existing codebase to epoxy
The migration is described as replacing the GL includes.
#include <GL/gl.h>
#include <GL/glx.h>
#include <GL/glext.h>with the epoxy equivalents.
#include <epoxy/gl.h>
#include <epoxy/glx.h>As long as epoxy's headers appear first, the README says you should be ready to go. That ordering requirement is the one detail to watch when you are converting a project that has its own include order, since the epoxy headers define the function pointers that the calls resolve through.
The second part of the migration is that some helpers become available, so you do not have to write them. epoxy_gl_version() returns the GL version as an integer, 12 for GL 1.2, 20 for GL 2.0, 44 for GL 4.4. And epoxy_has_gl_extension() returns whether a GL extension is available, with GL_ARB_texture_buffer_object given as the example.
The README adds a warning about that second helper: it is not terribly fast, so keep it out of your hot paths. That is a real constraint on how you structure the code, since the natural place to ask for an extension is at startup, and the natural place to call a function is inside the draw loop, and the two must not be confused.
Why not libGLEW
The README has a section comparing epoxy to GLEW, and the reasoning is the most useful thing in the document for anyone choosing a loader.
GLEW does not know about aliases of functions. The README gives the example that there are five providers of glPointParameterfv(), and you do not want to have to choose which one to call when they are all the same. That is the same problem epoxy solves.
GLEW does not support OpenGL ES. GLEW has a hard-to-maintain parser of extension specification text instead of using the old .spec file or the new .xml. GLEW has significant startup time overhead when glewInit() autodetects the world. And GLEW has a user-visible multithreading support choice for win32.
The history behind the project explains the rest. The motivation came out of previous use of libGLEW in piglit, and other GL dispatch code generation projects had similar failures. The goal was for piglit to be able to build a single binary for a test that can run on whatever context or window system it chooses, not based on link time choices.
Solving some of GLEW's problems for piglit meant replacing every single piece of GLEW, so the team built piglit-dispatch from scratch, and since they wanted to reuse it in other GL-related projects, epoxy is the result. That origin story explains a design decision that would otherwise look odd, which is that epoxy is a small library with a very particular set of requirements rather than a general framework.
The Windows context caveat
The known issues section is specific to Windows, and the reason is worth understanding because it changes how you integrate the library.
Automatic per-context symbol resolution on win32 requires that epoxy knows when wglMakeCurrent() is called, because wglGetProcAddress() returns values that depend on the context's device and pixel format. If wglMakeCurrent() is called from outside of epoxy, in a way that might change the device or pixel format, then epoxy needs to be notified using the epoxy_handle_external_wglMakeCurrent() function.
So on Windows, a wglMakeCurrent() call that epoxy did not make has to be followed by a call telling epoxy about it. That is a real integration constraint, and it is the kind of detail that turns into a debugging session if it is not known in advance, since the symptom is a dispatch table pointing at the wrong pixel format rather than a clean error.
The second Windows issue is a performance one, and the README is blunt about it: the win32 wglMakeCurrent() variants are slower than they should be, because they should be caching the resolved dispatch tables instead of resetting an entire thread-local dispatch table every time.
That last sentence also tells you something about the design, since the dispatch tables are thread-local. On Windows, making a context current tears down and rebuilds the thread's dispatch state, which is a cost you pay on context switches rather than on function calls.
Where libepoxy is the wrong choice
The first limitation is that epoxy does not remove the need to check for extensions. The README is clear on this, check for your extensions or versions being present before you use them, just like before. What epoxy adds is a diagnostic rather than a segfault, which is better but is not the same as the loader deciding for you. An application that assumes a function exists is still broken on the machine where it does not.
The second is the runtime cost of the dispatch itself. Every GL call through epoxy is an indirect call, and the version and extension queries are explicitly described as not fast. For a rendering hot path with thousands of draw calls per frame, that is measurable, and a project with a fixed known context could reasonably prefer direct linking against a specific GL library.
The third is the scope of the library. Epoxy is a function pointer layer and nothing else. It has no asset loading, no shader compilation helpers, no matrix library and no scene graph, so a project adopting it still has to decide about all of those separately.
The fourth is the Windows caveat, which is an integration tax rather than a defect. The external wglMakeCurrent notification and the dispatch table reset cost are real costs on a platform where the context model is different, and a project that only targets Linux would not notice either.
The fifth is the maintenance state of this particular repository, and it needs to be stated plainly. The last push here was on 2026-04-03, more than six months before today, so this tree is not receiving regular work. The published release history is older still, with 1.5.10 from March 2022 as the most recent release, before 1.5.9 and 1.5.8 in 2021. The README badge claims the MIT licence, and there is a COPYING file at the root, though GitHub itself lists the repository licence as one it cannot classify. A project considering this repository rather than the upstream distribution should check which tree it actually wants, because the release tags here are several years behind the last commit.
Licence, project shape and what a dispatch layer costs you
Epoxy presents itself as MIT licensed, with a licence badge in the README and a COPYING file in the repository root, even though GitHub's own classification of the repository licence is inconclusive. The MIT terms are the ones the upstream project uses, and a fork that changed the licensing terms would be visible in the COPYING file.
The project is the kind of library that is invisible once adopted. The application includes two headers and calls the same function names it always did, and nothing about the call sites has to change. That is why migration is framed as replacing includes rather than rewriting calls, and why the ability to mix with non-epoxy GL usage matters: a large codebase can be converted a file at a time.
The cost of a dispatch layer is therefore a dependency and a set of rules. The dependency is small, a C library with a narrow API and no runtime configuration to speak of. The rules are the parts that bite: epoxy's headers first, no version or extension queries in a hot path, and on Windows the notification call when you change the current context yourself.
For a project that already has a working loader and no portability problems, adopting epoxy is churn with no benefit. For a project that needs one codebase to run against desktop GL, GLES, EGL, GLX and WGL, the alias handling and the automatic initialisation remove work that would otherwise be a permanent maintenance burden, since every new GL version and every extension alias has to be dealt with by the loader rather than by you.
Editorial conclusion
libepoxy suits a project that wants one dispatch layer across desktop OpenGL, OpenGL ES, EGL, GLX and WGL without maintaining a generated extension parser of its own, since the alias handling alone removes a class of portability bugs. It does not suit a codebase that wants zero runtime lookup cost on the hot path, because the README is explicit that the query helpers are not fast. Verify the GL version and extension presence yourself before calling, and on Windows notify epoxy when you call wglMakeCurrent from outside it.
Frequently asked questions
What does libepoxy do?
It handles OpenGL function pointer management, hiding dlopen(), dlsym(), glXGetProcAddress() and eglGetProcAddress() from the application so you can call undecorated names like glCompileShader(). It supports GL 4.6 core and compatibility, GLES 1 to 3, and the EGL, GLX and WGL windowing systems.
How do I build libepoxy?
It builds with Meson and Ninja: create a build directory, run meson, then ninja and sudo ninja install. On Debian the dependencies are meson and libegl1-mesa-dev, and on macOS with MacPorts they are pkgconfig and meson.
How do I switch an existing project to use libepoxy?
Replace the GL includes such as GL/gl.h, GL/glx.h and GL/glext.h with epoxy/gl.h and epoxy/glx.h, and make sure the epoxy headers appear first in the include order. You can also mix epoxy and non-epoxy GL usage while converting the codebase gradually.
Why is libepoxy preferred over GLEW?
The README lists GLEW's problems: it does not know about function aliases, so you would have to choose between five providers of glPointParameterfv(), it does not support OpenGL ES, it has a hard-to-maintain parser of extension specification text, and glewInit() has significant startup overhead. Epoxy was built by replacing GLEW for piglit.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/anholt-libepoxy)Was this useful?