Skip to content
briar-systemsPublic

About

Mach bindings for OpenGL 4.6 core, generated from the Khronos registry

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

108 Commits

Folders and files

Repository files navigation

mach-gl

Mach bindings for OpenGL 4.6 core profile: a raw loaded-pointer layer plus an idiomatic Mach API on top, generated from the Khronos gl.xml registry. Project id is gl, so consumers reach everything as gl.*.

use glfw;
use glfwc: glfw.c;
use gl;
use std.types.error.err;
use std.types.option.opt;
use std.types.result.res;

fun example() err[glfw.Error] {
    val started: err[glfw.Error] = glfw.init();
    if (sel started.err) { ret started; }
    val opened: res[glfw.Window, glfw.Error] = glfw.open_window(1280, 720, "hello");
    if (sel opened.err) {
        glfw.terminate();
        ret err[glfw.Error].err{opened.err};
    }
    val w: glfw.Window = opened.ok;
    glfw.make_context_current(opt[glfw.Window].some{w});
    gl.load(glfwc.glfwGetProcAddress);
    for (!glfw.window_should_close(w)) {
        gl.clear_color(0.2, 0.3, 0.3, 1.0);
        gl.clear(gl.COLOR_BUFFER_BIT);
        glfw.swap_buffers(w);
        glfw.poll_events();
    }
    glfw.destroy_window(w);
    glfw.terminate();
    ret err[glfw.Error].ok{};
}

Consuming projects vendor the bindings as a normal Mach dependency. There is no system link requirement: every GL command is resolved at runtime through a loader the consumer provides (glfwGetProcAddress or any equivalent), so mach-gl declares no libs and forces none on consumers.

Add it with mach dep add, which selects the newest compatible release:

mach dep add . gl --git https://github.com/briar-systems/mach-gl

That declares it under its project id, gl, as a version range over releases:

[dep.gl]
git = "https://github.com/briar-systems/mach-gl"
version = "^0.7.0"

Requires Mach 6 and std 9.

Goals

  • Complete coverage of the OpenGL core profile, 1.0 through 4.6, generated mechanically from the pinned Khronos registry (tools/gl.xml).
  • Zero-cost: the idiomatic layer is thin wrappers over loaded function pointers; no allocation, no registries, no hidden state beyond the pointer table load fills.
  • Mach-idiomatic naming and types (snake_case, bool, str), mechanically derived so any GL reference maps directly.
  • Context-provider agnostic: anything that can produce a fun(*u8) ptr proc loader works.

Non-goals (v1)

  • The compatibility profile and anything core 4.6 removed (glBegin, matrix stack, ...).
  • Extensions (GL_ARB_*, ...): only commands and enums promoted to core.
  • OpenGL ES / SC.
  • A windowing or context layer: mach-glfw's job.

Architecture

Everything in src/ is generated by tools/gen.py from the vendored registry; tools/gen.py check fails if the committed sources drift.

src/
  c.mach        raw layer (generated): one pub var function pointer per GL
                command, C names and C-faithful types, plus load()
  enums.mach    constants (generated): every core enum, GL_ prefix stripped
  cmd.mach      idiomatic layer (generated): snake_case wrappers over c.*
  lib/
    gl.mach     library surface and artifact entry (generated): forwards
                every public symbol
tools/
  gen.py        registry generator; emits all generated sources
  gl.xml        pinned Khronos registry snapshot

Raw layer: gl.c

OpenGL commands cannot be linked: implementations export them only through a context's proc loader. The raw layer is therefore a table of module-level function pointers, one per command, with the C name and C-faithful signature:

pub var glClear: fun(u32) = nil;

load fills the table from a caller-supplied loader. Commands the running context does not export stay nil; calling one is the same contract as in C (don't call what the context version doesn't provide).

# resolve every core command through loader
# ---
# loader: maps a command name to its address, or nil if unsupported
# ret:    the number of commands resolved; unresolved pointers stay nil
pub fun load(loader: fun(*u8) ptr) i64;

Type mapping:

C Mach
GLenum, GLbitfield, GLuint u32
GLint, GLsizei i32
GLboolean, GLubyte, GLchar u8
GLbyte / GLshort / GLushort, GLhalf i8 / i16 / u16
GLfloat, GLclampf / GLdouble, GLclampd f32 / f64
GLint64 / GLuint64 i64 / u64
GLintptr, GLsizeiptr i64
GLsync, void* (and any opaque pointer) ptr
const GLchar* *u8
GLDEBUGPROC fun(u32, u32, u32, u32, i32, *u8, ptr)

Idiomatic layer

Naming is mechanically derived: commands are the C name minus the gl prefix, snake_cased (glBindBuffer → bind_buffer, glUniform4fv → uniform4fv); enums are the C macro minus only GL_ (GL_TRIANGLES → TRIANGLES). A stripped name that would start with a digit gets a leading _. Every name is globally unique, which lets lib/gl.mach flatten the whole API onto one namespace.

  • bool replaces GLboolean in parameters and returns; str replaces const GLchar* where the argument is a name string. Buffers, raw data pointers, and out-params keep C-faithful pointer types.
  • Scalar out-params stay out-params (get_integerv(MAJOR_VERSION, ?v)), the Mach idiom for multiple returns.
  • One convenience helper with no C counterpart: version(?major, ?minor), sugar over get_integerv, valid after load.

Error model is GL's own, not Result: get_error() wraps glGetError, and the 4.3 debug-callback path (debug_message_callback) is available on contexts that have it. This mirrors mach-glfw's rationale: GL already has a complete error model, and a Result wrap would cost ergonomics without adding information.

Library surface: gl.lib.gl

lib/gl.mach re-exports every public symbol of enums and cmd, plus c as a module (fwd gl.c;) so the raw table stays reachable as gl.c.glClear for anyone who wants C names. The [artifact.gl] static library is entered through that surface and marked default = true, which makes a bare use gl; resolve to it: gl.load(...), gl.clear(...), gl.COLOR_BUFFER_BIT. The surface also carries use std.runtime; so a library mach test links a runnable binary.

The convenience helper version(?major, ?minor) is generated into cmd.mach (sugar over get_integerv(MAJOR_VERSION/MINOR_VERSION), valid after load).

Generator

tools/gen.py (Python, stdlib only) parses tools/gl.xml, walks the <feature api="gl"> sets 1.0–4.6 applying core-profile removes, and emits all four generated sources deterministically. Layout comes from mach fmt - ($MACH_COMPILER, else mach on PATH), so generated sources pass mach fmt --check. tools/gen.py check diffs a fresh generation against the committed sources, so run it before committing and the registry pin and the committed bindings cannot drift apart.

Tests

test blocks are display-free: one enum of each emitted width, load with a nil loader (the table stays nil and reports 0) and with a stub loader that answers every name (it reports one resolution per request), and a call through the table into a Mach-implemented fake command, which pins the loaded-pointer call ABI without a GL context. Test fixtures are #[testing]. Paths that need a live context are exercised by downstream consumers, not mach test.

About

Mach bindings for OpenGL 4.6 core, generated from the Khronos registry

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages