Ziggified GLFW bindings with 100% API coverage, zero-fuss installation, cross compilation, and more.
Find a file
Lue 097bea84ab glfw: refactor getError and related functions
`getError()` now returns a struct `Error` containing `error_code` and
`description`. Rationale: retrieving the error code with the previous
implementation of `getError()` caused `getErrorString()` to return
null (the reverse is also true). The new implementation allows both
values to be retrieved at once.

The previous `getError()` function has been renamed to
`getErrorCode()` to reflect the fact that it returns a simple Zig
error rather than the `Error` struct. The error set returned by
`getErrorCode()` is now named `ErrorCode` rather than `Error`.

The behavior of the `getError()` family of functions clearing the
stored error is unchanged. However, since all code that used
`defer glfw.getError() catch {}` to explicitly clear errors had to be
refactored, a new `glfw.clearError()` function that returns void is
now available to make this operation more explicit.

Additionally, `mustGetError()` is now `mustGetErrorCode()`, and new
functions `mustGetError()` and `mustGetErrorString()` have been added
which wrap `getError()` and `getErrorString()` but panic if no error
is actually available.

Tests and API documentation had to be refactored across all of
`mach/glfw`. This commit also takes the opportunity to skip tests
involving window creation on CI so that other tests may still execute
normally.
2023-01-10 20:52:41 -07:00
.github all: update Zig to version 0.11.0-dev.1247+87b223428 2023-01-08 18:21:50 -07:00
src glfw: refactor getError and related functions 2023-01-10 20:52:41 -07:00
upstream@915548c869 glfw: update Vulkan headers 2022-08-07 22:05:00 -07:00
.gitattributes all: ensure .git* files are standard across subprojects 2022-08-23 08:36:51 -07:00
.gitignore glfw: fix native import (#583) 2022-10-16 01:19:12 -07:00
.gitmodules glfw: submodule path fix for subrepo 2022-06-15 06:59:56 -07:00
build.zig Revert "all: build: fix sdkPath for relative @src.file / fix autocompletion with ZLS / IDEs (#661)" 2023-01-10 01:57:52 -07:00
LICENSE LICENSE: note directories with a separate LICENSE file 2021-07-05 12:46:20 -07:00
LICENSE-APACHE initialize repository 2021-07-04 10:36:34 -07:00
LICENSE-MIT initialize repository 2021-07-04 10:36:34 -07:00
README.md glfw: refactor getError and related functions 2023-01-10 20:52:41 -07:00
system_sdk.zig system_sdk: update to latest commits 2022-12-18 18:02:31 -07:00

mach/glfw - Ziggified GLFW bindings CI Hexops logo

Ziggified GLFW bindings that Mach engine uses, with 100% API coverage, zero-fuss installation, cross compilation, and more.

This repository is a separate copy of the same library in the main Mach repository, and is automatically kept in sync, so that anyone can use this library in their own project / engine if they like!

Zero fuss installation, cross compilation, and more

Just as with Mach, you get zero fuss installation & cross compilation using these GLFW bindings. only zig and git are needed to build from any OS and produce binaries for every OS. No system dependencies at all.

100% API coverage, 130+ tests, etc.

These bindings have 100% API coverage of GLFW v3.3.4. Every function, type, constant, etc. has been wrapped in a ziggified API.

There are 130+ tests, and CI tests on all major platforms as well as cross-compilation between platforms:

platform support table

What does a ziggified GLFW API offer?

Why create a ziggified GLFW wrapper, instead of just using @cImport and interfacing with GLFW directly? You get:

  • true and false instead of c.GLFW_TRUE and c.GLFW_FALSE constants.
  • Generics, so you can just use window.hint instead of glfwWindowHint, glfwWindowHintString, etc.
  • Enums, always know what value a GLFW function can accept as everything is strictly typed. And use the nice Zig syntax to access enums, like window.getKey(.escape) instead of c.glfwGetKey(window, c.GLFW_KEY_ESCAPE)
  • Slices instead of C pointers and lengths.
  • packed structs represent bit masks, so you can use if (joystick.down and joystick.right) instead of if (joystick & c.GLFW_HAT_DOWN and joystick & c.GLFW_HAT_RIGHT), etc.
  • Methods, e.g. my_window.hint(...) instead of glfwWindowHint(my_window, ...)

How do I use OpenGL, Vulkan, WebGPU, etc. with this?

You'll need to bring your own library for this. Some are:

Examples

A minimal Vulkan example can be found in the mach-glfw-vulkan-example repository:

image

Getting started

Adding dependency (using Git)

In a libs subdirectory of the root of your project:

git clone https://github.com/hexops/mach-glfw

Then in your build.zig add:

...
const glfw = @import("libs/mach-glfw/build.zig");

pub fn build(b: *Builder) !void {
    ...
    exe.addPackage(glfw.pkg);
    try glfw.link(b, exe, .{});
}

(optional) Adding dependency using Gyro

gyro add --src github hexops/mach-glfw --root src/main.zig --alias glfw
gyro add --build_dep --src github hexops/mach-glfw --root build.zig --alias build-glfw

Then in your build.zig add:

...
const pkgs = @import("deps.zig").pkgs;
const glfw = @import("build-glfw");

pub fn build(b: *Builder) !void {
    ...

    exe.addPackage(pkgs.glfw);
    try glfw.link(b, exe, .{});
}

Note: You should use gyro build instead of zig build to use gyro

Next steps

Now in your code you may import and use GLFW:

const glfw = @import("glfw");

/// Default GLFW error handling callback
fn errorCallback(error_code: glfw.ErrorCode, description: [:0]const u8) void {
    std.log.err("glfw: {}: {s}\n", .{ error_code, description });
}

pub fn main() !void {
    glfw.setErrorCallback(errorCallback);
    if (!glfw.init(.{})) {
        std.log.err("failed to initialize GLFW: {?s}", .{glfw.getErrorString()});
        std.process.exit(1);
    }
    defer glfw.terminate();

    // Create our window
    const window = glfw.Window.create(640, 480, "Hello, mach-glfw!", null, null, .{}) orelse {
        std.log.err("failed to create GLFW window: {?s}", .{glfw.getErrorString()});
        std.process.exit(1);
    };
    defer window.destroy();

    // Wait for the user to close the window.
    while (!window.shouldClose()) {
        glfw.pollEvents();
    }
}

A warning about error handling

Unless the action you're performing is truly critical to your application continuing further, you should avoid terminating on error.

This is because GLFW unfortunately must return errors for a large portion of its functionality on some platforms, but especially for Wayland - so ideally your application is resiliant to such errors and merely e.g. logs failures that are not critical.

Here is a rough list of functionality Wayland does not support:

  • Window.setIcon
  • Window.setPos, Window.getPos
  • Window.iconify, Window.focus
  • Monitor.setGamma
  • Monitor.getGammaRamp, Monitor.setGammaRamp

For example, window.getPos() will always return x=0, y=0 on Wayland due to lack of platform support. Ignoring this error is a reasonable choice for most applications. However, errors like this can still be caught and handled:

const pos = window.getPos();

// Option 1: convert a GLFW error into a Zig error.
glfw.getErrorCode() catch |err| {
    std.log.err("failed to get window position: error={}", .{err});
    return err; // Or fall back to an alternative implementation.
};

// Option 2: log a human-readable description of the error.
if (glfw.getErrorString()) |description| {
    std.log.err("failed to get window position: {s}", .{description});
    // ...
}

// Option 3: use a combination of the above approaches.
if (glfw.getError()) |err| {
    const error_code = err.error_code; // Zig error
    const description = err.description; // Human-readable description
    std.log.err("failed to get window position: error={}: {s}", .{error_code, description});
    // ...
}

Note that the above example relies on GLFW's saved error being empty; otherwise, previously emitted errors may be mistaken for an error caused by window.getPos(). If your application frequently ignores errors, it may be necessary to call glfw.clearError() or defer glfw.clearError() to ensure a clean slate for future error handling.

Join the community

Join the Mach engine community on Matrix chat to discuss this project, ask questions, get help, etc.

Issues

Issues are tracked in the main Mach repository.

Contributing

Contributions are very welcome. Pull requests must be sent to the main repository to avoid some complex merge conflicts we'd get by accepting contributions in both repositories. Once the changes are merged there, they'll get sync'd to this repository automatically.

For now mach/glfw tracks the latest master revision of GLFW, as recorded in this file, as this version has critical undefined behavior fixes required for GLFW to work with Zig. We will switch to stable releases of GLFW once GLFW 3.4 is tagged.