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

@ -223,6 +223,9 @@ var in_mx0: int = 0 # x/y at the previous frame (for the delta)
var in_my0: int = 0
var in_mdx: int = 0 # delta this frame
var in_mdy: int = 0
var in_rdx: int = 0 # the raw motion the platform reports while captured
var in_rdy: int = 0
var in_cursor_mode: int = 0
var in_mbtn: int = 0 # button bitmask (bit 0 left, 1 right, 2 middle)
var in_wheel: int = 0 # wheel delta this frame
# gamepads: connected flag, button bitmask, and IN_AXES fixed axes each
@ -298,9 +301,11 @@ function input_device_commit(k: int, replaying: int) -> void {
# headless, from the single polled key. Injection (in_sim) is OR-ed on top.
if is_windowed() {
win_held(in_dev)
let mbuf = words(4) # [x, y, button-mask, wheel]
let mbuf = words(6) # [x, y, button-mask, wheel, raw dx, raw dy]
mbuf[4] = 0; mbuf[5] = 0
win_mouse(mbuf)
in_mx = mbuf[0]; in_my = mbuf[1]; in_mbtn = mbuf[2]; in_wheel = mbuf[3]
in_rdx = mbuf[4]; in_rdy = mbuf[5]
# #51 — feed the platform gamepad + touch state into the same buffers the
# read APIs use. Each is windowed-only glue (win_pad / win_touch are DCE'd
# in a headless build); on hardware they overwrite the injected state.
@ -334,6 +339,8 @@ function input_device_commit(k: int, replaying: int) -> void {
# platform above when windowed, by Input.set_mouse before this poll otherwise).
in_mdx = in_mx - in_mx0
in_mdy = in_my - in_my0
# captured (mode 2): the cursor is a clamped reticle, the motion is the raw delta
if is_windowed() and in_cursor_mode == 2 { in_mdx = in_rdx; in_mdy = in_rdy }
in_mx0 = in_mx
in_my0 = in_my
}
@ -438,6 +445,7 @@ enum CursorMode { Normal, Hidden, Locked, Confined } # Input.cursor_mode(mode:
enum PadButton { A, B, X, Y, LeftShoulder, RightShoulder, Back, Start } # Input.bind_pad(button:) / pad_button
enum MouseButton { Left, Right, Middle } # Input.mouse_down(button:)
function input_cursor_mode(mode: int) -> void {
in_cursor_mode = mode
if is_windowed() { win_cursor_mode(mode) }
}