From ce3986bb025688e6680c280aad1a0ab59716944e Mon Sep 17 00:00:00 2001 From: Orkuncakilkaya Date: Tue, 15 Sep 2026 11:33:01 +0300 Subject: [PATCH] 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 --- packages/ludic.render3d/actor.ludic | 14 +++--- packages/ludic.render3d/gpu.ludic | 69 ++++++++++++++++++++++++-- packages/ludic.render3d/grass.ludic | 2 +- packages/ludic.render3d/overlay.ludic | 6 +-- packages/ludic.render3d/post.ludic | 14 +++--- packages/ludic.render3d/prof.ludic | 10 ++-- packages/ludic.render3d/programs.ludic | 10 +--- packages/ludic.render3d/render.ludic | 11 ++-- packages/ludic.render3d/scatter.ludic | 12 ++--- packages/ludic.render3d/sky.ludic | 2 +- packages/ludic.render3d/terrain.ludic | 16 +++--- packages/ludic.render3d/water.ludic | 2 +- 12 files changed, 109 insertions(+), 59 deletions(-) diff --git a/packages/ludic.render3d/actor.ludic b/packages/ludic.render3d/actor.ludic index 86ff6f30..d7e6e0b0 100644 --- a/packages/ludic.render3d/actor.ludic +++ b/packages/ludic.render3d/actor.ludic @@ -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 } diff --git a/packages/ludic.render3d/gpu.ludic b/packages/ludic.render3d/gpu.ludic index b75d8665..86793d84 100644 --- a/packages/ludic.render3d/gpu.ludic +++ b/packages/ludic.render3d/gpu.ludic @@ -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 { diff --git a/packages/ludic.render3d/grass.ludic b/packages/ludic.render3d/grass.ludic index ffe1c904..23c96571 100644 --- a/packages/ludic.render3d/grass.ludic +++ b/packages/ludic.render3d/grass.ludic @@ -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) diff --git a/packages/ludic.render3d/overlay.ludic b/packages/ludic.render3d/overlay.ludic index de572a6d..61ce30c7 100644 --- a/packages/ludic.render3d/overlay.ludic +++ b/packages/ludic.render3d/overlay.ludic @@ -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 diff --git a/packages/ludic.render3d/post.ludic b/packages/ludic.render3d/post.ludic index fb24edb1..fed1592c 100644 --- a/packages/ludic.render3d/post.ludic +++ b/packages/ludic.render3d/post.ludic @@ -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) diff --git a/packages/ludic.render3d/prof.ludic b/packages/ludic.render3d/prof.ludic index 897c5904..e17489e6 100644 --- a/packages/ludic.render3d/prof.ludic +++ b/packages/ludic.render3d/prof.ludic @@ -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 diff --git a/packages/ludic.render3d/programs.ludic b/packages/ludic.render3d/programs.ludic index 069f0d6d..57d19efa 100644 --- a/packages/ludic.render3d/programs.ludic +++ b/packages/ludic.render3d/programs.ludic @@ -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=: 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 } diff --git a/packages/ludic.render3d/render.ludic b/packages/ludic.render3d/render.ludic index 4326c0ec..9dfe1fb8 100644 --- a/packages/ludic.render3d/render.ludic +++ b/packages/ludic.render3d/render.ludic @@ -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=: 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. diff --git a/packages/ludic.render3d/scatter.ludic b/packages/ludic.render3d/scatter.ludic index 25270e91..1efb2687 100644 --- a/packages/ludic.render3d/scatter.ludic +++ b/packages/ludic.render3d/scatter.ludic @@ -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) diff --git a/packages/ludic.render3d/sky.ludic b/packages/ludic.render3d/sky.ludic index 21de6f13..fee6ec86 100644 --- a/packages/ludic.render3d/sky.ludic +++ b/packages/ludic.render3d/sky.ludic @@ -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) diff --git a/packages/ludic.render3d/terrain.ludic b/packages/ludic.render3d/terrain.ludic index 853dec8c..5be84556 100644 --- a/packages/ludic.render3d/terrain.ludic +++ b/packages/ludic.render3d/terrain.ludic @@ -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) diff --git a/packages/ludic.render3d/water.ludic b/packages/ludic.render3d/water.ludic index 832fd8ab..bd4909bf 100644 --- a/packages/ludic.render3d/water.ludic +++ b/packages/ludic.render3d/water.ludic @@ -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)