Zigglgen, Zig OpenGL binding generator

After over a year of doing smaller personal projects I’m excited to finally present my first real Zig project that may actually be useful to someone else: zigglgen

zigglgen is an OpenGL binding generator that is written in Zig and fully integrated with the Zig package manager and build system. Adding zigglgen to your project is super easy:

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

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);

    b.installArtifact(exe);
}

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.
    windowing.makeContextCurrent(context);
    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.
    gl.makeProcTableCurrent(&procs);
    defer gl.makeProcTableCurrent(null);

    // Issue OpenGL commands to your heart's content!
    const alpha: gl.float = 1;
    gl.ClearColor(1, 1, 1, alpha);
    gl.Clear(gl.COLOR_BUFFER_BIT);
 }

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.

screenshot

I also wrote some more information about why OpenGL binding generators are necessary in a Zig NEWS article.

Even if you have no use for OpenGL you might want to refer to zigglgen as an example of a Zig package that generates code. Outside of the specifics of translating definitions from the OpenGL API registry to Zig, the implementation is relatively light and straightforward.

If you have any comments or questions, please leave them below and I’ll happily reply :smile:

24 Likes

Excited to try it out!

2 Likes

Thank you for creating the binding generator, but I am facing with an issue pairing with the zglfw binding.

Everything started by creating a context, which if I am not mistaken, it should be a window, and since I was using zglfw, I have created a window using the following:

const glfw = @import("zglfw");
const gl = @import("gl");
const zm = @import("zm");
const std = @import("std");

// constants and structs
var procs: gl.ProcTable = undefined;
const WindowSize = struct {
    pub const width: u32 = 800;
    pub const height: u32 = 600;
};

pub fn main() !void {
    // glfw initialization process:
    glfw.init() catch {
        std.log.err("GLFW initialization failed", .{});
        return;
    };

    glfw.windowHint(glfw.WindowHint.context_version_major, 3);
    glfw.windowHint(glfw.WindowHint.context_version_minor, 3);
    glfw.windowHint(glfw.WindowHint.opengl_profile, glfw.OpenGLProfile.opengl_core_profile);

    defer glfw.terminate();

    // create window with glfw
    const window = glfw.Window.create(WindowSize.width, WindowSize.height, "Opengl Triangle", null) catch {
        std.log.err("GLFW Window creation failed", .{});
        return;
    };

    defer window.destroy();

    // here is the main event loop to stay the window alive
    while (!glfw.windowShouldClose(window)) {
        glfw.swapBuffers(window);
        glfw.pollEvents();
    }
}

The code above works fine which I can create a blank window from glfw, but when I tried to initialize gl, injected the following code after defer window.destroy(); as shown:

    // Will try to find that on openfl sb7 to see if there are any explanations
    glfw.makeContextCurrent(window);
    defer glfw.makeContextCurrent(null);

    // Manage function pointers
    if (!procs.init(glfw.getProcAddress)) return error.InitFailed;

which aligns to your example:

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

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

It seems to fail to initialize the gl.ProcTable and returns the InitFailed error:

error: InitFailed
...\learn_opengl\src\main.zig:55:43: 0x7ff7bff6386b in main (learn_opengl.exe.obj)
    if (!procs.init(glfw.getProcAddress)) return error.InitFailed;
                                          ^
run
└─ run learn_opengl failure
error: the following command exited with error code 1:
...\learn_opengl\zig-out\bin\learn_opengl.exe 
Build Summary: 12/14 steps succeeded; 1 failed
run transitive failure
└─ run learn_opengl failure
error: the following build command failed with exit code 1:

Since I am new to openGL and using this binding generator; very likely, there are something significant missed from making this code to work, so I wish to know:

  • if anyone have successfully used zglfw with this binding generator for openGL
  • how could I troubleshoot for this issue if I only seem to know where but not how it fails.
  • since this is a binding generator, is that means: I need to located the original c code and headers somewhere in my computer and I need to reference it?

Update 1:
With a bit of tracing with a debugger, seems like when I pass glfw.getProcAddress into the init() function (in gl.zig), it fails to load these two ProcAddress, ended with a null:

  • GetnTexImage
  • GetnUniformdv

Seems they return a null from gl.getProcAddress() while it is not optional, so it switch the success variable in the init function to 0, failing the initialization process.

While I am going to further investigate the issue, let me know if anyone familiar with the issue, and let me know if further information is needed.

2 Likes

These commands are only available since OpenGL 4.5, but in the GLFW code you posted you are creating an OpenGL 3.3 context. Double-check which version you are specifying to your call to the generator in your build.zig. It appears that your system might not support the full OpenGL 4.5 API surface so you might want to lower the version, 3.3 and 4.1 are usually pretty well-supported on most systems. The generated file also exposes the version and profle via gl.info (e.g. gl.info.version_major) which you can use in your code if you want to ensure the context creation uses the correct versions.

2 Likes

Oh! This is it. Didn’t realized that context_version refer to the Opengl version which I thought it was something else.

Changing the hint to 4.5 doesn’t seems to fix the issue yet, but generating the binding with version .@“3.3” works, so this is definitely something done with the versioning.

Thank you so much! Let’s see if I can print my first triangle by today.

2 Likes

Glad you were able to get it to work! I’m not entirely satisfied with how initialization works currently and for situations like yours I wish the generator would provide more information about what exactly went wrong (wrong context version, missing functions, etc.) so that you can handle it better in code and suppress non-critical problems (e.g. a function you don’t even use not being available). My plan is to address this the next time I update the generator.

2 Likes

Good news, I have managed to plot my first triangle based on the one in the OpenGL Superbible 7:

Here is the example, and I will spend some time on cleaning and refactoring the code, making it easier to be understood about the pipeline and the steps we need to do before printing the triangle.

I am already really happy to see you have created this binding generator which it definitely helps me a lot on setting up the project with OpenGL, and wish you could find a way on improving the generator to make it more intuitive on tracing the initialization process.

1 Like