refactor(render3d): programs, GPU timers and the context behind gpu.ludic

The last of milestone 1: no OpenGL call is left in render3d outside gpu.ludic but the clock
and the CPU-side buffer helpers.

- gpu_program builds a program and remembers the variant it came from (vertex, fragment and
  defines as one line - the SPIR-V manifest's key), so a backend that cannot compile at run
  time finds the pipeline for the same handle. gpu_use_program and gpu_program_free replace
  32 uses and 2 frees; the overlay's program is recorded under overlay.vert|overlay.frag.
- gpu_query_new / _begin / _end / _result carry R3D_PROF's timers.
- gpu_open, gpu_vsync, gpu_renderer_name and gpu_resize_check carry the context.
- r3d_program_tess is gone: nothing called it and no tessellation shader exists.
- R3D_GLCHECK also checks after framebuffer and renderbuffer changes, viewport, draw buffers,
  program use, texture parameters, the resize check and between frames.

OpenGL frames are byte-identical at the five viewpoints; 59 self-tests pass with and without
R3D_GLCHECK, with no GL error; ludic-dev test 140 passed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-15 11:33:01 +03:00
parent 447acc11bd
commit ce3986bb02
12 changed files with 109 additions and 59 deletions

View file

@ -73,9 +73,9 @@ function r3d_env_flags() -> void {
function r3d_init(w: int, h: int, title: string) -> bool {
r3d_env_flags()
gpu_select()
if not gl_open(w, h, title) { print("r3d: no OpenGL context"); return false }
if Os.has_env("R3D_NOVSYNC") { gl_vsync(0) }
var renderer: string = gl_get_string(GL_RENDERER)
if not gpu_open(w, h, title) { print("r3d: no OpenGL context"); return false }
if Os.has_env("R3D_NOVSYNC") { gpu_vsync(0) }
var renderer: string = gpu_renderer_name()
print(`r3d: {gl_w}x{gl_h} on {renderer}`)
prof_init()
cam_init(fr(gl_w, gl_h))
@ -102,7 +102,7 @@ function r3d_draw_sky() -> void {
gpu_depth_write(false)
gpu_cull(false)
let p = r3d_sky_prog
gl_use_program(p)
gpu_use_program(p)
r3d_bind_2d(p, "u_sky", 0, sky_tex)
sky_bind_rot(p)
sky_bind_lighting(p)
@ -125,9 +125,10 @@ function r3d_resize() -> void {
print(`r3d: resized to {gl_w}x{gl_h}`)
}
function r3d_frame(time: int) -> void {
gpu_glcheck_after("the time between frames")
outline_frame()
if not r3d_ready { return }
if gl_resize_check() { r3d_resize() }
if gpu_resize_check() { r3d_resize() }
# R3D_RESIZE_AT=<frame>: rebuild every screen-sized buffer mid-run, as a window resize
# or a fullscreen change does. Headless has no window to resize, and this path is where
# a stale attachment or a texture freed twice shows up.