feat(gl): OpenGL 4.1 and the ludic.render3d renderer
`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:
parent
470971bf70
commit
f25289db20
90 changed files with 35316 additions and 19853 deletions
54
changes/terrain-perf.md
Normal file
54
changes/terrain-perf.md
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
bump: patch
|
||||
type: perf
|
||||
**The ground costs half what it did.** Measured in the Maroon Lake game, the terrain was 10.3 ms of
|
||||
a 22.2 ms frame; it is now 5.4 ms of 16.4 ms — 45 fps to 61 fps at 1080p, with the
|
||||
frame otherwise unchanged (every viewpoint tested stays above 54 dB PSNR against the
|
||||
old renderer, with no channel differing by more than 7/255).
|
||||
|
||||
**Measure by frame time, not by the pass timers.** `R3D_PROF`'s per-pass
|
||||
`GL_TIME_ELAPSED` queries cannot be trusted on this driver: with the ground's shading
|
||||
work removed the terrain query fell from 10.5 ms to 1.3 ms while the frame time did
|
||||
not move at all. Every number above and below is a median real frame time, taken by
|
||||
switching one thing off (`prof_ft_report`); the pass timers are still printed, and are
|
||||
still useful for spotting a pass that appears out of nowhere, but they cannot size one.
|
||||
`R3D_NOTERRAIN` skips the ground, `R3D_TNEARONLY` / `R3D_TFARONLY` draw every patch
|
||||
with one tier's program, and `R3D_RES=<w>x<h>` renders at another size — the three
|
||||
switches that say whether a cost is the ground, which tier it is in, and whether it is
|
||||
pixels at all.
|
||||
|
||||
**Each detail tier is its own program.** `terrain.frag` holds a detailed near tier and
|
||||
a cheap far one and chose between them per pixel, so every pixel of the valley walls
|
||||
was compiled — and scheduled — for a near path it never ran. CDLOD selection now knows
|
||||
which tiers a patch can contain: one that never comes within the split draws with
|
||||
`FAR_ONLY`, one wholly inside it with `NEAR_ONLY`, and only the ring of patches that
|
||||
straddle the band needs the program that holds both and cross-fades. Pixel-identical,
|
||||
and it makes the tiers separately measurable: the near tier costs 7.5 ms over a whole
|
||||
frame, the far tier 2.3 ms.
|
||||
|
||||
**The sun visibility is its own pass** (`tersun.frag`). The same CDLOD patches are
|
||||
rasterised once into a screen-sized R8 buffer that holds nothing but each ground
|
||||
pixel's sun visibility, and `terrain.frag` fetches it by fragment coordinate. The pass
|
||||
costs 0.27 ms, shares the frame's depth buffer so it doubles as a depth prepass, and
|
||||
takes the cascade read out of the shader that covers the screen. It picks its tier —
|
||||
filtered PCF near, a single tap far — over the same cross-faded band the ground uses,
|
||||
so the boundary is not a contour you can find on the hillside.
|
||||
|
||||
**Nothing is sampled for a weight of zero.** The ground sampled all four of its
|
||||
materials for every pixel and then blended three of them at zero: a meadow pixel took
|
||||
nine taps of triplanar rock, a cliff pixel nine taps of stochastic grass, and every
|
||||
pixel in the valley took the snow tile and the four noise fields behind the lake's
|
||||
shore wash — a wash that is a hairline along one shore within 120 m of the camera. The
|
||||
survey photograph's classification now runs first, because it is what decides which
|
||||
materials are present; each material block sits behind its own weight; the ridge field
|
||||
that ragged the snow line is skipped 160 m below it, where it cannot change anything;
|
||||
and inside the stochastic blend a cell's rotation, offset and rotated gradients are
|
||||
computed inside its own test, so a cell whose sharpened weight rounds away costs
|
||||
nothing. All of it exact where the weight is zero, and it is most of the win.
|
||||
|
||||
Material sampling is what remains (2.8 ms of the 5.4): scanned 2K tiles taken at 16x
|
||||
anisotropy on ground seen at a grazing angle. `R3D_ANISO=<n>` sets the filter (the
|
||||
default is unchanged at 16; 4 is worth 1.0 ms and 1 is worth 1.7 ms).
|
||||
|
||||
The shadow pass is 1.2 ms of the frame and has nothing to give: re-using the far
|
||||
cascades between frames — their windows are snapped to a 14 m and a 64 m grid — is
|
||||
worth 0.15 ms standing still and nothing while walking, so it is not in the tree.
|
||||
Loading…
Add table
Add a link
Reference in a new issue