feat(gl): OpenGL 4.1 and the ludic.render3d renderer
Some checks failed
ci / build-and-test (push) Waiting to run
commit-lint / conventional-commits (push) Waiting to run
bootstrap / cfree-fixpoint (push) Has been cancelled
docs / build-and-deploy (push) Successful in 34s

`Gl.*` binds the whole OpenGL 4.1 core API — every entry point of the
platform gl3.h with every GL_* constant, generated by `ludic-dev glgen`
with per-call ABI thunks. Windowed builds get an NSOpenGLContext on the
existing window at Retina resolution; headless builds render into an
offscreen CGL context, so a program that uses Gl.* renders and
screenshots identically under the test harness. It links gl.ll, the
thunks and OpenGL.framework only when used; every other build stays
byte-identical.

packages/ludic.render3d is a physically based renderer written on that
surface: HDRI image-based lighting, GPU-generated terrain with scanned
PBR materials, CDLOD, cascaded shadows, glTF with skinning, instanced
vegetation with impostors, procedural grass, water, SSAO, and an HDR
pipeline with bloom, auto-exposure and ACES.

It also carries this session's work on it: the terrain at half its cost
(10.3 -> 5.4 ms of frame), the streaming hitch that got worse the longer
you played, a resize that emptied the world, and the packaging that lets
a game use the renderer from its own repository — `ludic assets`, the
material manifest shipping with the package, and shader lookup falling
back to the install root. See changes/ for each, with its numbers.

The camping game that drove all of it has moved out to its own
repository, Maroon Lake; examples/rendering/smooth.ludic stays as the
renderer's example here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-10 03:31:12 +03:00
parent 470971bf70
commit f25289db20
90 changed files with 35316 additions and 19853 deletions

View file

@ -0,0 +1,37 @@
---
id: gl
title: Gl
order: 60
---
OpenGL for Ludic. <code>Gl.*</code> binds the <strong>whole OpenGL 4.1 core API</strong>: every <code>gl*</code> entry point of the platform <code>gl3.h</code> is a method named by its snake case (<code>glBindBuffer</code> → <code>Gl.bind_buffer</code>, <code>glTexImage2D</code> → <code>Gl.tex_image2d</code>, <code>glDrawElementsInstanced</code> → <code>Gl.draw_elements_instanced</code>), with every <code>GL_*</code> constant available as written. Parameters keep the header's names as labels (<code>glVertexAttribPointer</code>'s <code>pointer</code> is called <code>offset</code>, <code>program</code> is <code>prog</code>); <code>GLfloat</code>/<code>GLdouble</code> parameters take <code>fixed</code>, pointer parameters take the raw <code>bytes</code>/<code>words</code> buffers Ludic already has, and 64-bit sizes take <code>long</code> (an <code>int</code> widens). The binding is generated by <code>ludic-dev glgen</code> from the header, with one ABI thunk per entry point.
A context comes from <code>Gl.open(width, height, title)</code>: windowed, an <code>NSOpenGLContext</code> on the game's window at the display's backing resolution (2× on Retina — <code>Gl.width()</code>/<code>Gl.height()</code> are the drawable's pixels); headless, an offscreen context with a framebuffer standing in for the screen (<code>Gl.screen_fbo()</code>), so the same program renders and screenshots under the test harness. <code>Gl.swap()</code> presents, <code>Gl.screenshot(path)</code> writes the screen as a PPM, <code>Gl.check(tag)</code> prints any pending error.
The glue: <code>Gl.program(vs, fs)</code> / <code>Gl.program5(vs, tcs, tes, gs, fs)</code> compile and link (logs on failure), <code>Gl.uniform(prog, name)</code>, <code>Gl.vao()</code>, <code>Gl.buffer()</code>, <code>Gl.texture()</code>, <code>Gl.framebuffer()</code> allocate, and float data is filled from Q16.16 with <code>Gl.floats(n)</code> / <code>Gl.put(buf, i, v)</code> / <code>Gl.bytes_of(n)</code> or from IEEE bits with <code>Gl.put_bits</code>; <code>Gl.f32(v)</code> and <code>Gl.fixed(bits)</code> convert single values. The bare <code>f_add</code>/<code>f_mul</code>/… helpers do IEEE single-precision arithmetic on those bit patterns for programs that want real floats on the CPU.
Using <code>Gl.*</code> links <code>gl.ll</code>, the thunks and <code>OpenGL.framework</code>; a program that does not is byte-identical to before. The <code>ludic.render3d</code> package is a physically based 3D renderer written on this surface (see <code>examples/rendering/smooth.ludic</code> here, and <a href="https://git.workshopsoft.io/workshopsoft/maroon-lake">Maroon Lake</a> for a game built on it).
```ludic
program Triangle {
property Marker { on: int = 1 }
model Anchor { Marker }
var prog: int = 0
var vao: int = 0
handler Boot phase Start {
spawn Anchor {}
if not Gl.open(width: 640, height: 360, title: "GL") { quit() }
prog = Gl.program(vs: "#version 410 core\nvoid main(){ gl_Position = vec4(float(gl_VertexID == 1) * 2.0 - 0.5, float(gl_VertexID == 2) * 2.0 - 0.5, 0.0, 1.0); }\n",
fs: "#version 410 core\nout vec4 o; void main(){ o = vec4(1.0, 0.5, 0.2, 1.0); }\n")
vao = Gl.vao()
}
handler Draw phase Render {
Gl.clear_color(red: 0.1, green: 0.1, blue: 0.15, alpha: 1.0)
Gl.clear(mask: GL_COLOR_BUFFER_BIT)
Gl.use_program(prog: prog)
Gl.bind_vertex_array(array: vao)
Gl.draw_arrays(mode: GL_TRIANGLES, first: 0, count: 3)
Gl.swap()
}
}
```