Mach v0.2 has been released! For all the details check out the announcement

Migration notes

To learn more about Mach’s library stability guarantees, check out the libraries overview page. This page provides migration guides for Mach libraries-walking you through how to update your code to the latest version.

mach-core: build API improvements (2023-09-24)

Imports now have a mach- prefix:

-const core = @import("core");
+const core = @import("mach-core");

The module() helper (you likely do not use this) has been replaced with a proper Zig module accessible via b.dependency("mach_core", .{...}).module("mach-core")

mach-core: build API improvements (2023-09-17)

Zig 0.12.0-dev.389+61b70778b is now in use (previously 0.12.0-dev.21+ac95cfe44); and your build.zig.zon file no longer needs to specify transitive dependencies, and the build.zig API has changed slightly:

mach-core: API design changes (2023-08-07)


Instead of writing e.g. this before:

const title = try std.fmt.bufPrintZ(&core.title, "Sprite2D [ FPS: {d} ]", .{@floor(1 / delta_time)});

It is now possible to write just:

try core.printTitle("Sprite2D [ FPS: {d} ]", .{@floor(1 / delta_time)});

Delta time, frame rate

Internal timers now provide reasonable default methods of measuring frame rate, input rate, and frame delta time.

  • core.frameRate()
  • core.inputRate()
  • core.delta_time (f32 seconds)
  • core.delta_time_ns (u64 nanoseconds)

mach.Core is now a global

  • Your App struct no longer needs to have a field core: mach.Core, (before it was required, and the name MUST be core)
  • APIs that were accessible through a *Core instance before, e.g. app.core.frameRate(), are now accessible as global methods. e.g. @import("core").frameRate()
  • core.init no longer needs an allocator, instead core.allocator is used - which you can change at any point before calling core.init()


pub const App = @This();

var gpa = std.heap.GeneralPurposeAllocator(.{}){};

core: mach.Core,

pub fn init(app: *App) !void {
    try app.core.init(gpa.allocator(), .{});
    app.* = .{
        // ...

pub fn deinit(app: *App) void {
    defer _ = gpa.deinit();
    defer app.core.deinit();
    // ...

pub fn update(app: *App) !bool {
    // ...
    return false;


pub const App = @This();

pub fn init(app: *App) !void {
    try core.init(.{});
    app.* = .{
        // ...

pub fn deinit(app: *App) void {
    defer core.deinit();
    // ...

pub fn update(app: *App) !bool {
    // ...
    return false;

Custom entrypoints

It’s now easier than ever to write a custom entrypoint for your application (e.g. pub fn main) if you really want/need to:

Fields instead of getters

A few getters have been turned into fields instead:

  • core.device() -> core.device
  • core.device().getQueue() -> core.queue
  • core.descriptor() -> core.descriptor
  • core.adapter() -> core.adapter
  • core.swapChain() -> core.swap_chain

mach-core: build system changes

The following fields/functions from mach.App in build.zig have been removed:

  • .step
  • .install()
  • .addRunArtifact()
  • .getInstallStep()
  • .link(.{ .gpu_dawn_options = .{} })

The following have been added:

  • .compile.step
  • .install.step
  • .run.step
  • App.init(.{ .gpu_dawn_options = .{} })

Suggested usage is now e.g.:

const app = try core.App.init(...);

const install_step = b.step("install", "Install " ++;

const run_step = b.step("run", "Run " ++;

It is also possible to add a ‘compile only’ step now, for e.g. checking code merely builds should you want to:

const compile_step = b.step("compile", "Install " ++;

mach-core: multithreaded rendering & standalone usage

mach-core is now available as a 100% standalone repository / Zig package. The getting started documentation has been updated to reflect this.

Additionally, we have landed multi-threaded rendering support which allows native applications to run at e.g. 60FPS while handling input events at 240hz. It also enables butter-smooth window resizing.

API changes:

  • core.framebufferSize() has been removed in favor of core.descriptor().width and core.descriptor().height
  • App.init and App.deinit are executed on the main thread, App.update is executed in a separate thread.
  • Input events are enqueued/bufferred, and you can poll them whenever you like (e.g. you may handle input mid-frame if you like.)
  • setWaitTimeout / “waiting” APIs are being redesigned and are not yet functional. e.g. for the case of a desktop app that you rightfully want to be low-power, in this scenario you need a way to slow event polling of both the main thread and rendering thread - which is not yet possible.

mach-glfw: package manager usage

If you are a user of mach-glfw, note that we have adopted the experimental Zig package manager. It is not perfect yet and there are many papercuts; for details on how to update your codebase please see this

mach-core: v0.2 API redesign

2023-01-27 - affects all mach-core users

Mach v0.2 brings a complete redesign of the mach-core API. To upgrade your application see the upgrade guide

mach-glfw: error handling improvements

2023-01-10 - affects all mach-glfw users

We’ve made another error handling improvement to the mach-glfw API:

  • glfw.getError has been renamed to glfw.getErrorCode (returns a Zig error type still)
  • glfw.getError instead now returns a struct with both the error message and Zig error type.
  • glfw.clearError has been added, a smaller helper to make writing defer glfw.clearError() nicer than before (defer glfw.getErrorCode() catch {};).

glfw: error handling changes

2023-01-08 - affects all mach-glfw users

We have completely overhauled the mach-glfw error handling approach to help users better avoid footguns and ultimately improve the ability of Zig GLFW applications to run on more obscure X11 window managers and Wayland in general.