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-glThat 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.
- 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
loadfills. - 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) ptrproc loader works.
- 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.
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
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) |
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.
boolreplacesGLbooleanin parameters and returns;strreplacesconst 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 overget_integerv, valid afterload.
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.
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).
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.
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.