Cover image for Announcing zigglgen, Zig OpenGL binding generator
Carl Åstholm
Carl Åstholm

Posted on

Announcing zigglgen, Zig OpenGL binding generator

OpenGL and binding generators

OpenGL is an interesting graphics API. Unlike many other libraries which you link your program with at compile or load time, OpenGL is implemented as a set of functions that your program must find at run time. The various OpenGL specs define the functions that exist, what each of their names and signatures are and which symbolic constants can be passed to them, but doesn't specify how to locate them and instead leaves that to your OS and/or graphics drivers.

In practice, the way you most commonly locate these functions is through a function exposed by your windowing system, usually called something like getProcAddress. If your program wants to use the DrawArrays OpenGL function, you obtain a pointer to it via getProcAddress("glDrawArrays") (OpenGL functions are prefixed with gl in C and when loading them).

A non-trivial OpenGL program might use several dozen different functions, so you might imagine that writing all of this function loading code yourself is a bit of a pain. The way most developers solve this problem is by using a binding generator that uses information from the official OpenGL API registry to generate all this boilerplate code in advance. glad is a popular example of one such generator for C.

But what about Zig?


zigglgen is (to my knowledge) the first OpenGL binding generator that is written in Zig and fully integrated with the Zig package manager and build system. zigglgen is licensed under the MIT License and super simple to add to your project:

1. Run zig fetch to add the zigglgen package to your build.zig.zon manifest:

zig fetch https://github.com/castholm/zigglgen/releases/download/v0.1.0/zigglgen.tar.gz --save
Enter fullscreen mode Exit fullscreen mode

2. Generate a set of OpenGL bindings in your build.zig build script:

const std = @import("std");

pub fn build(b: *std.Build) void {
    const exe = b.addExecutable(...);

    // Choose the OpenGL API, version, profile and extensions you want to generate bindings for.
    const gl_bindings = @import("zigglgen").generateBindingsModule(b, .{
        .api = .gl,
        .version = .@"4.1",
        .profile = .core,
        .extensions = &.{ .ARB_clip_control, .NV_scissor_exclusive },

    // Import the generated module.
    exe.root_module.addImport("gl", gl_bindings);

Enter fullscreen mode Exit fullscreen mode

3. Initialize OpenGL and start issuing commands:

const windowing = @import(...);
const gl = @import("gl");

// Procedure table that will hold OpenGL functions loaded at runtime.
var procs: gl.ProcTable = undefined;

pub fn main() !void {
    // Create an OpenGL context using a windowing system of your choice.
    var context = windowing.createContext(...);
    defer context.destroy();

    // Make the OpenGL context current on the calling thread.
    defer windowing.makeContextCurrent(null);

    // Initialize the procedure table.
    if (!procs.init(windowing.getProcAddress)) return error.InitFailed;

    // Make the procedure table current on the calling thread.
    defer gl.makeProcTableCurrent(null);

    // Issue OpenGL commands to your heart's content!
    const alpha: gl.float = 1;
    gl.ClearColor(1, 1, 1, alpha);
Enter fullscreen mode Exit fullscreen mode

For a complete example project that creates a window using
mach-glfw and draws a triangle to it, check out the zigglgen-example/ subdirectory of the main zigglgen repository.

The intended way to use zigglgen is to generate bindings at build time. zigglgen plays nicely with the Zig build system cache and will only re-generate them when needed. But if you prefer, it is also perfectly possible to generate bindings ahead of time and commit them to revision control. zigglgen-example/build.zig demonstrates both of these approaches.

What's next?

Because the OpenGL API registry (which zigglgen sources its information from) is written with C in mind, it does not encode enough information about pointer types for zigglgen to know whether any one pointer is * single-item, [*] many-item, [*:0] sentinel-terminated or ?* optional. As a result, it has no choice but to translate them as [*c] C pointers.

A long-term goal for zigglgen is for every single pointer type to be correctly typed. zigglgen currently defines a small number of overrides, but the plan is add even more continuously. You are welcome to submit patches that help us extend these overrides! There are approximately 3300 different commands defined in the API registry, and if we work together, we can get rid of the last [*c] C pointer sooner.

If you have even the slightest question, comment, suggestion or criticism, please leave them below! I'm also very eager to hear if you end up using zigglgen in your projects and if there's anything I can improve to make your experience with it better. If you prefer, you can also leave comments directly in the accompanying Ziggit post.

Top comments (2)

flipbit profile image

Cool project, thanks for sharing!

Stupid question regarding the pointer ambiguities: Would it make sense to fork the API registry and add the necessary pointer infos in a parsable but non breaking way so not just Zig but any other language with similar capabilities for better pointers could also benefit from this effort? I'm not knowledgeable about the subject, so I'm sorry if this is completely unrealistic and I do not mean to take anything away from what you are working on.

castholm profile image
Carl Åstholm

That would be possible, but I haven't really come across any other projects that have expressed a need for this (though I will admit I haven't looked very hard). There are very few languages that let you annotate pointer types to the level of detail that Zig does and most bindings I've seen for other languages seem to focus less on translating C code directly and more on how represent integer handles as high-level classes/objects, or grouping constants into enums (which is something zigglgen doesn't do; see the FAQ in the README for an explanation on why).

It should also be mentioned that the XML API registry does encode some pointer information (mostly about single/many-item), but the registry README itself calls it a "poorly defined syntax" and Khronos has no interest in ensuring that they are correct because their own C header generators don't care for them, so I opted to ignore them.

The way zigglen works is that there's a PowerShell script that I run every once in a while to fetch the latest updates to the rather bulky XML API registry and convert it to structured Zig code. It's possible that we could change this step and instead have it output JSON or ZON or some more interchangeable format; either way, both the API registry data and the overrides are very structured so they should be easy to move around or convert to different formats if some other programming language ecosystem expressed interest in benefitting from the Zig overrides.