Open-source project
rust-windowing/gl-rs avatar
rust-windowing/gl-rs

gl-rs, the OpenGL function loader and binding generator behind Rust's graphics stack

An OpenGL function pointer loader for Rust

748 stars128 forksRustApache-2.0

At a glance

What is it?
rust-windowing/gl-rs is a three-crate repository: the gl crate that loads OpenGL function pointers at runtime, gl_generator which generates bindings from the Khronos API registry, and khronos_api which exposes that registry as byte string constants. The generated bindings cover OpenGL, OpenGL ES and EGL, and the registry data arrives as a git submodule.
Who is it for?
Adopt gl-rs if you are writing Rust that calls OpenGL directly and want bindings that match the current Khronos registry rather than a hand-maintained wrapper. Skip it if a higher level crate already wraps the graphics API for you, since gl-rs is the bottom layer.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 123 days ago.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The three crates and what each one is for

This repository contains the building blocks for OpenGL wrapper libraries, and the README points at each crate's own README for detail. The split is the thing to understand.

The gl crate is an OpenGL function pointer loader for Rust, and it is the crate most people use directly:

toml
[dependencies]
gl = "0.14.0"

A loader rather than a wrapper is the important distinction. OpenGL is a dynamically linked library whose entry points are resolved at runtime, so a binding crate has to provide a way to load those pointers and then call through them, rather than linking against them at compile time. That is what gl is.

gl_generator is the code generator for creating bindings to the Khronos OpenGL APIs, and it is a build dependency rather than a runtime one:

toml
[build-dependencies]
gl_generator = "0.14.0"

khronos_api is the Khronos XML API Registry exposed as byte string constants, and it is also a build dependency:

toml
[build-dependencies]
khronos_api = "3.1.0"

So the layering is: the registry describes the API, the generator turns the registry into Rust source, and the generated source becomes the loader crate you depend on. A project that ships its own OpenGL wrapper runs the generator against the registry and produces a crate of its own, and a project that just wants to call OpenGL depends on gl directly.

The versions are staggered, which is normal for this shape of project: gl and gl_generator move together at 0.14.0, while khronos_api is at 3.1.0 because it tracks the registry rather than the bindings.

The licence is Apache-2.0, and each crate carries its own licence file in the repository.

What the Khronos registry actually contains

The registry is the heart of the project, and its size explains several design choices.

The Khronos Group maintains the API registry as a set of XML files that describe the OpenGL, OpenGL ES and EGL interfaces. Every function has a name, a signature, a return type, and a record of which versions and extensions it belongs to. That last part is the part that makes the registry awkward to consume directly: the same function can be present in OpenGL 3.3 core, in an extension in one vendor's implementation and in a different extension elsewhere, and the bindings have to reflect that rather than flatten it.

Exposing the registry as byte string constants, which is what khronos_api does, is a way of making the data available to a build script without a parser dependency in the main crate. The build reads the bytes, the generator interprets them, and the generated Rust is what ends up in your binary.

The practical consequence is that the version of the bindings you get is the version of the registry they were generated from, and that is a separate axis from the version of the gl crate. The staggered version numbers in the README, 0.14.0 for gl and gl_generator and 3.1.0 for khronos_api, reflect that the registry has its own release cadence.

The extension handling is worth calling out too, because it is where a binding crate earns its keep. If you only need the core profile, a small hand-written crate is enough. If your application uses a compute shader, a debug output, a timer query or any of the many extensions a modern driver exposes, you need the extension surface, and generating it from the registry is the only way to keep it current.

The registry as a submodule and why the build breaks without it

The compiling from source section is two lines long and the second one matters:

sh
git submodule update --init

khronos_api makes use of git submodules, and you need to initialise them before building. The reason is straightforward once you understand what the crate contains. The Khronos API registry is not a small file; it is a set of XML files describing every OpenGL, OpenGL ES and EGL function, its parameters, its return type and its availability across versions and extensions. That data has to come from somewhere, and rather than vendoring a copy into the repository, the project pulls it as a submodule.

The consequence for you is that a plain git clone is not enough to build, and a CI job that forgets the submodule update step fails in a way that looks like a missing file rather than a missing step. It is the single most common way to get stuck with this repository.

The workspace layout shows the scale of the test suite, which is the other thing worth noting. The Cargo.toml lists the gl, gl_generator and khronos_api members, plus six separate test crates: test_add_registries, test_gen_symbols, test_no_warnings, test_symbols, test_unstable_api and test_with_extensions.

Those names describe the work. Adding registries is tested separately from generating symbols, and the symbol generation is tested separately from the symbols themselves. test_no_warnings means the generated code is checked to compile without warnings, which is a meaningful bar for generated Rust, because generated code that produces warnings trains people to ignore warnings in their own code. test_with_extensions covers the extension handling, and test_unstable_api covers the unstable parts of the registry.

The workspace lint configuration is one line, uninlined_format_args set to deny, which is a small sign that the project runs current clippy rules against its own generated output.

Loading pointers and the step people forget

Using the loader has one step that is easy to miss and hard to debug: the pointers have to be loaded before you can call anything.

OpenGL's entry points are resolved by the driver at runtime, so a loader has to fetch each function pointer from the current context before the first call to it. Typically this happens by asking the platform for the current context and then loading the function pointers for that context's version and extensions.

The failure mode when you skip it is a crash on the first call rather than a clear error, and the practical habit is to load everything once at startup, right after you have a context, and to check the version you got. A context on a machine with an old driver may not support the version your code assumes, and the check is what turns a mysterious crash into a message.

The other detail that catches people is the distinction between a core function and an extension function. Loading a core function is straightforward. Loading an extension function requires the extension to be present and enabled, and if it is not, the pointer load fails. In practice that means checking for the extension before calling it, and having a fallback path.

None of this is exotic, but it is the reason the generated code is structured the way it is. The generator knows, from the registry, exactly which functions belong to which version and which extensions, so it can produce a loader that separates the two and makes the extension case explicit rather than leaving every application to work it out.

How it compares with the alternatives

There are two other routes to calling OpenGL from Rust, and the choice comes down to how much of the API you need.

The first is a higher-level wrapper such as wgpu or gfx-rs, or a game engine built on one of them. These abstract the API, and in exchange they give you a modern design, portability beyond the OpenGL API surface, and a much smaller amount of unsafe code. If you are building something that renders rather than something that specifically drives OpenGL, this is the right answer and gl-rs is irrelevant to you.

The second is a hand-written binding crate, or a binding crate generated by a different tool. Some of these cover only the core profile of OpenGL, which is a much smaller set of functions, and they are adequate for a lot of work. The advantage of gl-rs is coverage: because the generator works from the Khronos registry, the extensions are there, and the versions are handled.

There is also the option of binding to OpenGL through the system library with a build script and link attributes, rather than loading pointers at runtime. That produces faster calls, since the calls are direct rather than through a function pointer, but it hard-links against a specific OpenGL implementation at build time, which means the binary only runs where that implementation exists. On Linux with Mesa that is usually fine; on Windows the OpenGL implementation is a system component that varies by driver and hardware, and linking directly is a portability problem. The loader approach is the portable one, and it is what gl is.

The cost of the loader is a pointer indirection on every call and the need to initialise the pointers before use, which is a step that can be forgotten. For a graphics workload the indirection is usually not the bottleneck, but it does mean gl-rs is not what you would choose for a compute-heavy path where the call overhead is measurable.

The comparison with the other Khronos binding generators is worth mentioning too, since there are several. The distinguishing feature of gl-rs is that it is the one the Rust graphics ecosystem actually settled on, so the wrappers built on it, and the examples that use it, are the ones you will find when you search for how to do something.

Editorial conclusion

Adopt gl-rs if you are writing Rust that calls OpenGL directly and want bindings that match the current Khronos registry rather than a hand-maintained wrapper. Skip it if a higher level crate already wraps the graphics API for you, since gl-rs is the bottom layer. Verify first by running git submodule update --init, since the registry data is a submodule and the build fails without it, and then check that your OpenGL version is covered by the generated extensions.

Frequently asked questions

What is gl-rs and which crates does it contain?

It is a repository of building blocks for OpenGL wrapper libraries in Rust: the gl crate, an OpenGL function pointer loader, the gl_generator code generator for creating bindings to the Khronos OpenGL APIs, and khronos_api, the Khronos XML API Registry exposed as byte string constants.

Why does building gl-rs from source need a git submodule update?

Because khronos_api uses git submodules to bring in the Khronos API registry data, which describes the OpenGL, OpenGL ES and EGL functions. Without running git submodule update --init the registry data is missing and the build fails.

What are the gl, gl_generator and khronos_api crates used for?

The registry describes the API, gl_generator turns the registry into Rust source, and the generated source becomes the gl loader you depend on. A project shipping its own OpenGL wrapper runs the generator; a project that just calls OpenGL adds gl = 0.14.0 as a dependency.

When should I use gl-rs instead of a higher level graphics wrapper?

When you specifically need to drive OpenGL itself, including its extensions, and accept the function pointer indirection and the initialisation step. If you are building something that renders rather than something that specifically uses OpenGL, a wrapper such as wgpu or a game engine is the better choice.

How is the gl-rs project tested?

The workspace has six separate test crates covering adding registries, generating symbols, symbol correctness, absence of warnings, the unstable API and extension handling, and the workspace lints deny the uninlined_format_args clippy rule against the generated code.

Official sources

  1. Issues
  2. License: Apache-2.0
  3. README
  4. rust-windowing/gl-rs on GitHub
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.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/rust-windowing-gl-rs.svg)](https://hysenlabs.com/projects/rust-windowing-gl-rs)

Was this useful?