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

@ -72,7 +72,7 @@ function ac_prog_new(vs: string, fs: string, defs: string) -> AcProg {
a.l_view = gpu_uniform(p, "u_view"); a.l_proj = gpu_uniform(p, "u_proj"); a.l_mh = gpu_uniform(p, "u_model_h")
a.l_out = gpu_uniform(p, "u_outline"); a.l_ocol = gpu_uniform(p, "u_outline_col")
# the samplers never move: units 0, 1, 2
gl_use_program(p)
gpu_use_program(p)
u_i(gpu_uniform(p, "u_diff"), 0); u_i(gpu_uniform(p, "u_nrm"), 1); u_i(gpu_uniform(p, "u_arm"), 2)
return a
}
@ -132,7 +132,7 @@ function ac_bind_scene(ap: AcProg) -> void {
if ap.scene_frame == ac_frame { return }
ap.scene_frame = ac_frame
let p = ap.prog
gl_use_program(p)
gpu_use_program(p)
u_mat4(ap.l_view, cam_view)
u_mat4(ap.l_proj, cam_proj)
u_f(ap.l_mh, F_ZERO)
@ -158,7 +158,7 @@ function ac_visible(a: Actor, shadow: bool) -> bool {
}
function actor_draw_one(a: Actor, ap: AcProg, shadow: bool) -> void {
let p = ap.prog
gl_use_program(p)
gpu_use_program(p)
u_mat4(ap.l_model, a.mat)
var skinned = F_ZERO
if a.skin != null { skinned = F_ONE; u_mat4n(ap.l_bones, a.skin.n_joints, a.skin.bones) }
@ -265,7 +265,7 @@ function actor_draw_outlines() -> void {
if a.outline == 0 or not ac_visible(a, false) { continue }
var ap = ac_out
if a.cutout { ap = ac_out_cut }
gl_use_program(ap.prog)
gpu_use_program(ap.prog)
u_mat4(ap.l_view, cam_view); u_mat4(ap.l_proj, cam_proj)
u_f(ap.l_out, f_mul(a.outline, a.scale))
if a.ocol != null { u_v3(ap.l_ocol, a.ocol) } else { u_f3(ap.l_ocol, F_ONE, F_ONE, F_ONE) }
@ -275,7 +275,7 @@ function actor_draw_outlines() -> void {
for i in 0 .. ac_oq_n {
let q = ac_oq[i]
let ap = ac_out
gl_use_program(ap.prog)
gpu_use_program(ap.prog)
u_mat4(ap.l_view, cam_view); u_mat4(ap.l_proj, cam_proj)
u_f(ap.l_out, q.width)
u_f3(ap.l_ocol, q.r, q.g, q.b)
@ -302,8 +302,8 @@ function actor_draw_outline_one(a: Actor, ap: AcProg) -> void {
}
function actor_draw_casters(light_vp: words) -> void {
if ac_actors == null { return }
gl_use_program(ac_sh.prog); u_mat4(ac_sh.l_lvp, light_vp)
gl_use_program(ac_sh_cut.prog); u_mat4(ac_sh_cut.l_lvp, light_vp)
gpu_use_program(ac_sh.prog); u_mat4(ac_sh.l_lvp, light_vp)
gpu_use_program(ac_sh_cut.prog); u_mat4(ac_sh_cut.l_lvp, light_vp)
for i in 0 .. len(ac_actors) {
let a = ac_actors[i]
if not ac_visible(a, true) { continue }

View file

@ -157,11 +157,13 @@ var gpu_s_scissor: int = -1
function gpu_scissor(x: int, y_top: int, w: int, h: int) -> void {
if gpu_s_scissor != 1 { gpu_s_scissor = 1; gl_enable(GL_SCISSOR_TEST) }
gl_scissor(x, gl_h - y_top - h, w, h)
gpu_glcheck_after("scissor")
}
function gpu_scissor_off() -> void {
if gpu_s_scissor == 0 { return }
gpu_s_scissor = 0
gl_disable(GL_SCISSOR_TEST)
gpu_glcheck_after("scissor off")
}
# ---- uniforms --------------------------------------------------------------------
@ -171,6 +173,54 @@ function gpu_scissor_off() -> void {
# are looked up by their first element ("u_bones[0]"), as every GL driver accepts.
function gpu_uniform(prog: int, name: string) -> int { return gl_get_uniform_location(prog, name) }
# ---- programs ----------------------------------------------------------------------
# A program remembers the variant it was built from - vertex file, fragment file and the
# defines on one line, the key the SPIR-V manifest uses - which is how a backend that cannot
# compile shaders at run time finds its pipeline for the same handle.
var gpu_prog_ids: []int = null
var gpu_prog_keys: []string = null
var gpu_prog_cur: int = 0
function gpu_program(vs_src: string, fs_src: string, vs: string, fs: string, defines: string) -> int {
let p = gl_program(vs_src, fs_src)
if p == 0 { return 0 }
if gpu_prog_ids == null { gpu_prog_ids = new []int; gpu_prog_keys = new []string }
push(gpu_prog_ids, p)
push(gpu_prog_keys, `{vs}|{fs}|{Text.replace(defines, "\n", ";")}`)
return p
}
# the manifest key a program was built from; "" for one this layer did not build
function gpu_program_key(p: int) -> string {
if gpu_prog_ids == null { return "" }
for i in 0 .. len(gpu_prog_ids) { if gpu_prog_ids[i] == p { return gpu_prog_keys[i] } }
return ""
}
function gpu_use_program(p: int) -> void { gl_use_program(p); gpu_prog_cur = p; gpu_glcheck_after("use program") }
function gpu_program_free(p: int) -> void {
if p == 0 { return }
gl_delete_program(p)
if gpu_prog_cur == p { gpu_prog_cur = 0 }
if gpu_prog_ids != null { for i in 0 .. len(gpu_prog_ids) { if gpu_prog_ids[i] == p { gpu_prog_ids[i] = 0; gpu_prog_keys[i] = "" } } }
gpu_glcheck_after("program free")
}
# ---- GPU timers (R3D_PROF) ----------------------------------------------------------
function gpu_query_new(n: int, ids: words) -> void { gl_gen_queries(n, ids) }
function gpu_query_begin(id: int) -> void { gl_begin_query(GL_TIME_ELAPSED, id) }
function gpu_query_end() -> void { gl_end_query(GL_TIME_ELAPSED) }
# true once the query has its result; the nanoseconds (low 32 bits) are then in out[0]
function gpu_query_result(id: int, out: words) -> bool {
gl_get_query_objectiv(id, GL_QUERY_RESULT_AVAILABLE, out)
if out[0] == 0 { return false }
gl_get_query_objectui64v(id, GL_QUERY_RESULT, out)
return true
}
# ---- the context --------------------------------------------------------------------
function gpu_open(w: int, h: int, title: string) -> bool { return gl_open(w, h, title) }
function gpu_vsync(on: int) -> void { gl_vsync(on) }
function gpu_renderer_name() -> string { return gl_get_string(GL_RENDERER) }
function gpu_resize_check() -> bool { let r = gl_resize_check(); gpu_glcheck_after("the resize check"); return r }
var gpu_u_tmp: words = null
function gpu_tmp() -> words { if gpu_u_tmp == null { gpu_u_tmp = words(4) }; return gpu_u_tmp }
# float bits (IEEE singles in an int), like every other number in the renderer
@ -516,7 +566,7 @@ function gpu_tex_bind(kind: int, tex: int) -> void {
}
}
# pixel transfer packing (alignment, byte swap) for the uploads and read-backs that follow
function gpu_pixel_store(pname: int, value: int) -> void { gl_pixel_storei(pname, value) }
function gpu_pixel_store(pname: int, value: int) -> void { gl_pixel_storei(pname, value); gpu_glcheck_after("pixel store") }
function gpu_tex_image2d(ifmt: int, w: int, h: int, fmt: int, ty: int, data: pointer) -> void {
gl_tex_image2d(GL_TEXTURE_2D, 0, ifmt, w, h, 0, fmt, ty, data)
gpu_glcheck_after(`a {w}x{h} texture upload (format {ifmt})`)
@ -539,12 +589,14 @@ function gpu_tex_param(kind: int, pname: int, value: int) -> void {
if pname == GL_TEXTURE_WRAP_T { gpu_tx[o + 8] = value }
if pname == GL_TEXTURE_COMPARE_MODE { if value == GL_NONE { gpu_tx[o + 9] = 0 } }
if pname == GL_TEXTURE_COMPARE_FUNC { gpu_tx[o + 9] = value }
gpu_glcheck_after("tex param")
}
# a float parameter (fixed, as Gl.* takes it): anisotropy is the one the renderer sets
function gpu_tex_paramf(kind: int, pname: int, value: fixed) -> void {
gl_tex_parameterf(gpu_gl_target(kind), pname, value)
let o = gpu_tx_at(gpu_bound(kind))
if o >= 0 and pname == 0x84FE { gpu_tx[o + 11] = fx_to_f32(value) }
gpu_glcheck_after("tex paramf")
}
# the border colour clamp-to-border reads (four fixed values in `rgba`)
function gpu_tex_border(kind: int, rgba: pointer) -> void { gl_tex_parameterfv(gpu_gl_target(kind), GL_TEXTURE_BORDER_COLOR, rgba) }
@ -639,9 +691,10 @@ function gpu_fb_new() -> int { return gl_framebuffer() }
function gpu_fb_bind(fb: int) -> void {
gl_bind_framebuffer(GL_FRAMEBUFFER, fb)
gpu_fb_cur = fb
gpu_glcheck_after("fb bind")
}
function gpu_fb_bind_read(fb: int) -> void { gl_bind_framebuffer(GL_READ_FRAMEBUFFER, fb) }
function gpu_fb_bind_draw(fb: int) -> void { gl_bind_framebuffer(GL_DRAW_FRAMEBUFFER, fb) }
function gpu_fb_bind_read(fb: int) -> void { gl_bind_framebuffer(GL_READ_FRAMEBUFFER, fb); gpu_glcheck_after("fb bind read") }
function gpu_fb_bind_draw(fb: int) -> void { gl_bind_framebuffer(GL_DRAW_FRAMEBUFFER, fb); gpu_glcheck_after("fb bind draw") }
function gpu_fb_color(slot: int, tex: int) -> void {
gl_framebuffer_texture2d(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + slot, GL_TEXTURE_2D, tex, 0)
let o = gpu_fb_at(gpu_fb_cur)
@ -677,6 +730,7 @@ function gpu_rb_storage(rb: int, ifmt: int, w: int, h: int, samples: int) -> voi
if samples > 0 { gl_renderbuffer_storage_multisample(GL_RENDERBUFFER, samples, ifmt, w, h) }
else { gl_renderbuffer_storage(GL_RENDERBUFFER, ifmt, w, h) }
gpu_rb_samples = samples
gpu_glcheck_after("rb storage")
}
function gpu_fb_color_rb(slot: int, rb: int) -> void {
gl_framebuffer_renderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + slot, GL_RENDERBUFFER, rb)
@ -693,11 +747,13 @@ function gpu_fb_draw_buffers(n: int) -> void {
if gpu_drawbufs == null { gpu_drawbufs = words(8) }
for i in 0 .. n { gpu_drawbufs[i] = GL_COLOR_ATTACHMENT0 + i }
gl_draw_buffers(n, gpu_drawbufs)
gpu_glcheck_after("fb draw buffers")
}
# a depth-only target: no colour is drawn or read
function gpu_fb_no_color() -> void {
gl_draw_buffer(GL_NONE)
gl_read_buffer(GL_NONE)
gpu_glcheck_after("fb no color")
}
function gpu_fb_status() -> int { return gl_check_framebuffer_status(GL_FRAMEBUFFER) }
function gpu_fb_free(fb: int) -> void {
@ -707,14 +763,16 @@ function gpu_fb_free(fb: int) -> void {
gl_delete_framebuffers(1, ids)
let o = gpu_fb_at(fb)
if o >= 0 { for i in 0 .. GPU_FB_W { gpu_fb[o + i] = 0 } }
gpu_glcheck_after("fb free")
}
function gpu_rb_free(rb: int) -> void {
if rb == 0 { return }
let ids = gpu_tmp()
ids[0] = rb
gl_delete_renderbuffers(1, ids)
gpu_glcheck_after("rb free")
}
function gpu_viewport(x: int, y: int, w: int, h: int) -> void { gl_viewport(x, y, w, h) }
function gpu_viewport(x: int, y: int, w: int, h: int) -> void { gl_viewport(x, y, w, h); gpu_glcheck_after("viewport") }
# R3D_GLCHECK: before a draw or a clear, the bound framebuffer must be complete; after it, no
# error may be pending. Each report names the framebuffer and what was being done, once per
@ -765,9 +823,10 @@ function gpu_blit(w: int, h: int, mask: int) -> void {
}
# the framebuffer the finished frame is presented from (an offscreen one, headless)
function gpu_screen_fb() -> int { return gl_screen }
function gpu_multisample(on: bool) -> void { gpu_gl_cap(GL_MULTISAMPLE, gpu_b(on)) }
function gpu_multisample(on: bool) -> void { gpu_gl_cap(GL_MULTISAMPLE, gpu_b(on)); gpu_glcheck_after("multisample") }
function gpu_wireframe(on: bool) -> void {
if on { gl_polygon_mode(GL_FRONT_AND_BACK, GL_LINE) } else { gl_polygon_mode(GL_FRONT_AND_BACK, GL_FILL) }
gpu_glcheck_after("wireframe")
}
# the presented frame as RGB8, bottom row first (a photograph)
function gpu_read_screen(w: int, h: int, out: pointer) -> void {

View file

@ -118,7 +118,7 @@ function grass_tiles(size: int, d_min: int, d_max: int) -> void {
function grass_draw() -> void {
if not grass_on or ter_reflect or grass_prog == 0 { return }
let p = grass_prog
gl_use_program(p)
gpu_use_program(p)
u_mat4(gpu_uniform(p, "u_view"), cam_view)
u_mat4(gpu_uniform(p, "u_proj"), cam_proj)
u_mat4(gpu_uniform(p, "u_vp"), cam_vp_clean)

View file

@ -53,7 +53,7 @@ var ov_clip_h: int = 0
function overlay_init(font_dir: string) -> bool {
r3d_program_log("overlay.vert", "overlay.frag", "")
ov_prog = gl_program("#version 410 core\n" + r3d_shader_file("overlay.vert"), "#version 410 core\n" + r3d_shader_file("overlay.frag"))
ov_prog = gpu_program("#version 410 core\n" + r3d_shader_file("overlay.vert"), "#version 410 core\n" + r3d_shader_file("overlay.frag"), "overlay.vert", "overlay.frag", "")
if ov_prog == 0 { print("overlay: program failed"); return false }
ov_mesh = gpu_mesh_new()
ov_vbo = gpu_mesh_vertices(ov_mesh, null, gl_bytes_of(OV_MAX_QUADS * 6 * OV_FLOATS), GPU_DYNAMIC)
@ -161,7 +161,7 @@ function ov_begin() -> void {
gpu_cull(false)
gpu_blend(true)
gpu_blend_func(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
gl_use_program(ov_prog)
gpu_use_program(ov_prog)
u_f2(gpu_uniform(ov_prog, "u_screen"), fi(gl_w), fi(gl_h))
ov_n = 0; ov_nr = 0; ov_range_start = 0
ov_tex = ov_white
@ -195,7 +195,7 @@ function ov_flush() -> void {
# framebuffer operation. Saying the target at each flush is what a render pass says anyway.
gpu_fb_bind(gpu_screen_fb())
gpu_viewport(0, 0, gl_w, gl_h)
gl_use_program(ov_prog)
gpu_use_program(ov_prog)
gpu_mesh_bind(ov_mesh)
gpu_buffer_upload(ov_vbo, gl_bytes_of(ov_n * 6 * OV_FLOATS), ov_buf, GPU_STREAM)
var last = -1

View file

@ -142,7 +142,7 @@ function post_measure() -> void {
let next = 1 - post_adapt_i
target_bind(post_adapt_t[next])
gpu_depth_test(false)
gl_use_program(post_p_adapt)
gpu_use_program(post_p_adapt)
r3d_bind_2d(post_p_adapt, "u_scene", 0, post_hdr.color)
r3d_bind_2d(post_p_adapt, "u_prev", 1, post_adapt_t[post_adapt_i].color)
u_f(gpu_uniform(post_p_adapt, "u_lod"), fi(post_mips - 1))
@ -217,7 +217,7 @@ function post_ssao_pass() -> void {
gpu_depth_test(false)
gpu_blend(false)
target_bind(post_ao)
gl_use_program(post_p_ao)
gpu_use_program(post_p_ao)
r3d_bind_2d(post_p_ao, "u_depth", 0, post_hdr.depth)
r3d_bind_2d(post_p_ao, "u_prev_color", 1, post_prev.color)
u_f(gpu_uniform(post_p_ao, "u_frame"), fi(post_frame % 64))
@ -228,7 +228,7 @@ function post_ssao_pass() -> void {
u_f(gpu_uniform(post_p_ao, "u_intensity"), post_ao_intensity)
mesh_draw(post_fs)
target_bind(post_ao_blur)
gl_use_program(post_p_ao_blur)
gpu_use_program(post_p_ao_blur)
r3d_bind_2d(post_p_ao_blur, "u_ao", 0, post_ao.color)
r3d_bind_2d(post_p_ao_blur, "u_depth", 1, post_hdr.depth)
u_f2(gpu_uniform(post_p_ao_blur, "u_texel"), fr(1, post_ao.w), fr(1, post_ao.h))
@ -240,7 +240,7 @@ function post_bloom_pass() -> void {
gpu_blend(false)
var src = post_color
var sw = post_w; var sh = post_h
gl_use_program(post_p_down)
gpu_use_program(post_p_down)
for i in 0 .. BLOOM_LEVELS {
let t = post_bloom[i]
target_bind(t)
@ -252,7 +252,7 @@ function post_bloom_pass() -> void {
mesh_draw(post_fs)
src = t.color; sw = t.w; sh = t.h
}
gl_use_program(post_p_up)
gpu_use_program(post_p_up)
gpu_blend(true)
gpu_blend_func(GL_ONE, GL_ONE)
var i = BLOOM_LEVELS - 1
@ -273,7 +273,7 @@ function post_tonemap(color_tex: int) -> void {
if post_auto { post_measure() }
target_bind(post_ldr)
gpu_depth_test(false)
gl_use_program(post_p_tone)
gpu_use_program(post_p_tone)
r3d_bind_2d(post_p_tone, "u_hdr", 0, color_tex)
r3d_bind_2d(post_p_tone, "u_bloom", 1, post_bloom[0].color)
r3d_bind_2d(post_p_tone, "u_ao", 2, post_ao_blur.color)
@ -294,7 +294,7 @@ function post_tonemap(color_tex: int) -> void {
# sharpen + grain onto the screen
gpu_fb_bind(gpu_screen_fb())
gpu_viewport(0, 0, gl_w, gl_h)
gl_use_program(post_p_sharp)
gpu_use_program(post_p_sharp)
r3d_bind_2d(post_p_sharp, "u_src", 0, post_ldr.color)
u_f2(gpu_uniform(post_p_sharp, "u_texel"), fr(1, post_w), fr(1, post_h))
u_f(gpu_uniform(post_p_sharp, "u_amount"), post_sharpen)

View file

@ -32,7 +32,7 @@ function prof_init() -> void {
prof_hits = new []long
prof_ids = words(PROF_SLOTS * PROF_RING)
prof_scratch = words(4)
gl_gen_queries(PROF_SLOTS * PROF_RING, prof_ids)
gpu_query_new(PROF_SLOTS * PROF_RING, prof_ids)
prof_n = 0
prof_frame = 0
prof_active = -1
@ -59,13 +59,13 @@ function prof_begin(name: pointer) -> void {
let s = prof_slot(name)
if s < 0 { return }
prof_active = s
gl_begin_query(GL_TIME_ELAPSED, prof_ids[s * PROF_RING + (prof_frame % PROF_RING)])
gpu_query_begin(prof_ids[s * PROF_RING + (prof_frame % PROF_RING)])
}
function prof_end() -> void {
if not prof_on { return }
if prof_active < 0 { return }
gl_end_query(GL_TIME_ELAPSED)
gpu_query_end()
prof_active = -1
}
@ -78,9 +78,7 @@ function prof_collect() -> void {
var s = 0
while s < prof_n {
let q = prof_ids[s * PROF_RING + slot_frame]
gl_get_query_objectiv(q, GL_QUERY_RESULT_AVAILABLE, prof_scratch)
if prof_scratch[0] != 0 {
gl_get_query_objectui64v(q, GL_QUERY_RESULT, prof_scratch)
if gpu_query_result(q, prof_scratch) {
# the low 32 bits are ample: a pass is far below 4 seconds
prof_ns[s] = prof_ns[s] + prof_scratch[0]
prof_hits[s] = prof_hits[s] + 1

View file

@ -56,14 +56,6 @@ function r3d_shader_src(name: string, defines: string, is_frag: bool) -> string
return s + r3d_shader_file(name)
}
# Build a program with tessellation control/evaluation between vertex and fragment.
function r3d_program_tess(vs: string, tcs: string, tes: string, fs: string, defines: string) -> int {
let p = gl_program5(r3d_shader_src(vs, defines, false), r3d_shader_src(tcs, defines, false),
r3d_shader_src(tes, defines, false), null, r3d_shader_src(fs, defines, true))
if p == 0 { print(`r3d: tess program failed: {vs} + {tcs} + {tes} + {fs}`) }
return p
}
# Build a program from a vertex + fragment file pair (defines apply to both).
# R3D_PROGRAMS_LOG=<file>: append every program built (vertex, fragment, defines) - the list a
# backend that cannot compile shaders at run time (Vulkan: SPIR-V is built ahead) must cover
@ -81,7 +73,7 @@ function r3d_program_log(vs: string, fs: string, defines: string) -> void {
}
function r3d_program(vs: string, fs: string, defines: string) -> int {
r3d_program_log(vs, fs, defines)
let p = gl_program(r3d_shader_src(vs, defines, false), r3d_shader_src(fs, defines, true))
let p = gpu_program(r3d_shader_src(vs, defines, false), r3d_shader_src(fs, defines, true), vs, fs, `{r3d_global_defs}{defines}`)
if p == 0 { print(`r3d: program failed: {vs} + {fs}`) }
return p
}

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.

View file

@ -445,7 +445,7 @@ function impostor_bake(model: Model, tiles: int, tw: int, th: int) -> Impostor {
let hh = f_mul(model.height, F_HALF)
var bake = sc_bake_prog
if sc_bake_flower { bake = sc_bake_flower_prog }
gl_use_program(bake)
gpu_use_program(bake)
for t in 0 .. tiles {
let a = f_mul(f_mul(F_TWO, F_PI), fr(t, tiles))
v3_set(at, F_ZERO, cy, F_ZERO)
@ -772,7 +772,7 @@ function layer_draw_near(l: Layer, shadow: bool, light_vp: words, full: bool) ->
function layer_draw_model(l: Layer, model: Model, vb: int, cnt: int, card: bool, shadow: bool, light_vp: words) -> void {
if cnt == 0 { return }
let p = layer_program(l, shadow, card)
gl_use_program(p)
gpu_use_program(p)
# over the prepass: only the fragment the prepass kept, at exactly its depth (a texel
# it cut would otherwise pass LEQUAL over the terrain behind and draw the quad solid)
if p == sc_prog_fol_eq { gpu_depth_func(GL_EQUAL) }
@ -831,7 +831,7 @@ function layer_draw_far(l: Layer, shadow: bool, light_vp: words) -> void {
if l.n_far == 0 or l.imp == null { return }
var p = sc_imp_prog
if shadow { p = sc_imp_prog_shadow }
gl_use_program(p)
gpu_use_program(p)
let im = l.imp
u_f(gpu_uniform(p, "u_radius"), im.radius)
u_f(gpu_uniform(p, "u_height"), im.height)
@ -867,7 +867,7 @@ function layer_draw_far(l: Layer, shadow: bool, light_vp: words) -> void {
function layer_draw_shadow(l: Layer, light_vp: words) -> void {
if l.n_sh == 0 or l.imp == null { return }
let p = sc_imp_prog_shadow
gl_use_program(p)
gpu_use_program(p)
let im = l.imp
u_f(gpu_uniform(p, "u_radius"), im.radius)
u_f(gpu_uniform(p, "u_height"), im.height)
@ -891,7 +891,7 @@ var sc_skip_card: bool = false
function layer_draw_depth(l: Layer, model: Model, vb: int, cnt: int) -> void {
if cnt == 0 or model == null or vb == 0 { return }
let p = sc_prog_fol_depth
gl_use_program(p)
gpu_use_program(p)
var ground = F_ZERO
if l.grounded { ground = F_ONE }
u_f(gpu_uniform(p, "u_ground"), ground)
@ -1028,7 +1028,7 @@ function carpet_bake(layers: []Layer, count: int, tile: int, res: int) -> int {
m4_look_at(view, eye, at, up)
m4_ortho(proj, f_neg(half), half, f_neg(half), half, fl(0.1), fi(12))
let bake = sc_bake_card_prog
gl_use_program(bake)
gpu_use_program(bake)
u_mat4(gpu_uniform(bake, "u_view"), view)
u_mat4(gpu_uniform(bake, "u_proj"), proj)
u_f(gpu_uniform(bake, "u_wind"), F_ZERO)

View file

@ -74,7 +74,7 @@ function sky_convolve(prog: int, target_tex: int, layer: int, w: int, h: int, ro
if layer < 0 { gpu_fb_color(0, target_tex) }
else { gpu_fb_color_layer(0, target_tex, layer) }
gpu_viewport(0, 0, w, h)
gl_use_program(prog)
gpu_use_program(prog)
r3d_bind_2d(prog, "u_sky", 0, sky_tex)
sky_bind_rot(prog)
u_f(gpu_uniform(prog, "u_sun_clip"), hdr_clip)

View file

@ -227,7 +227,7 @@ function terrain_generate() -> void {
gpu_fb_color(0, ter_height_tex)
gpu_viewport(0, 0, TERRAIN_RES, TERRAIN_RES)
gpu_depth_test(false)
gl_use_program(p)
gpu_use_program(p)
u_f(gpu_uniform(p, "u_half"), fi(TERRAIN_HALF))
if ter_dem_tex != 0 {
r3d_bind_2d(p, "u_dem", 0, ter_dem_tex)
@ -248,11 +248,11 @@ function terrain_generate() -> void {
ter_height_tex = tex_target(TERRAIN_RES, TERRAIN_RES, GL_RGBA32F, GL_RGBA, GL_FLOAT, GL_LINEAR)
gpu_fb_color(0, ter_height_tex)
let pn = r3d_program("fullscreen.vert", "ternormal.frag", "")
gl_use_program(pn)
gpu_use_program(pn)
r3d_bind_2d(pn, "u_src", 0, raw)
u_f(gpu_uniform(pn, "u_half"), fi(TERRAIN_HALF))
mesh_draw(sky_fullscreen)
gl_delete_program(pn)
gpu_program_free(pn)
gpu_tex_free(raw)
# read the heights back for placement
ter_heights = words(TERRAIN_RES * TERRAIN_RES)
@ -261,7 +261,7 @@ function terrain_generate() -> void {
gpu_tex_read(GPU_TEX2D, GL_RED, GL_FLOAT, ter_heights)
gpu_fb_bind(0)
gpu_fb_free(fbo)
gl_delete_program(p)
gpu_program_free(p)
gpu_check("terrain generate")
}
@ -285,7 +285,7 @@ function terrain_bake_shadow() -> void {
gpu_viewport(0, 0, TERRAIN_SHADOW_RES, TERRAIN_SHADOW_RES)
gpu_depth_test(false)
gpu_blend(false)
gl_use_program(p)
gpu_use_program(p)
r3d_bind_2d(p, "u_height", 0, ter_height_tex)
u_f(gpu_uniform(p, "u_half"), fi(TERRAIN_HALF))
u_v3(gpu_uniform(p, "u_sun"), sun_dir)
@ -433,7 +433,7 @@ function terrain_draw_shadow(light_vp: words) -> void {
# Every per-frame uniform of one terrain program. Both tiers are bound up front so
# selection can switch between them per patch without re-binding anything but the node.
function terrain_bind_prog(p: int) -> void {
gl_use_program(p)
gpu_use_program(p)
r3d_bind_2d(p, "u_height", 0, ter_height_tex)
r3d_bind_2d(p, "u_grass_d", 1, ter_tex[0]); r3d_bind_2d(p, "u_grass_n", 2, ter_tex[1]); r3d_bind_2d(p, "u_grass_a", 3, ter_tex[2])
# The cliff maps went with the dead cliff sample. Binding textures for uniforms the
@ -525,7 +525,7 @@ function terrain_sun_pass(w: int, h: int, depth: int) -> Target {
gpu_clear_color(1.0, 1.0, 1.0, 1.0) # unshadowed where nothing is drawn
gpu_clear(GL_COLOR_BUFFER_BIT)
let p = ter_sun_prog
gl_use_program(p)
gpu_use_program(p)
r3d_bind_2d(p, "u_height", 0, ter_height_tex)
u_f(gpu_uniform(p, "u_half"), fi(TERRAIN_HALF))
u_mat4(gpu_uniform(p, "u_view"), cam_view)
@ -717,7 +717,7 @@ function cdlod_draw(level: int, ix: int, iz: int) -> void {
if ter_force_near { p = ter_prog_near }
if p == ter_prog_far { cd_far_draws += 1 }
if p == ter_prog_near { cd_near_draws += 1 }
if p != ter_prog_cur { gl_use_program(p); ter_prog_cur = p }
if p != ter_prog_cur { gpu_use_program(p); ter_prog_cur = p }
let t = gl_scratch()
t[0] = x0; t[1] = z0; t[2] = size
u_v3(gpu_uniform(p, "u_node"), t)

View file

@ -137,7 +137,7 @@ function water_init(level: int, cx: int, cz: int, ex: int, ez: int) -> void {
function water_draw(depth_tex: int) -> void {
if not water_on or wb_n == 0 { return }
let p = water_prog
gl_use_program(p)
gpu_use_program(p)
u_mat4(gpu_uniform(p, "u_view"), cam_view)
u_mat4(gpu_uniform(p, "u_proj"), cam_proj)
u_mat4(gpu_uniform(p, "u_inv_vp"), cam_inv_vp)