From 2c427816d94cb692db8220ecf3a574556c94d0a6 Mon Sep 17 00:00:00 2001 From: Orkuncakilkaya Date: Tue, 15 Sep 2026 11:43:55 +0300 Subject: [PATCH] feat(render3d): r3d_present and r3d_screenshot - the end of a frame through the layer A game ended its frame with Gl.swap() and shot it with Gl.screenshot, which tied it to OpenGL and made it name Gl.* only so the parser would splice the GL runtime. gpu_present and gpu_screenshot carry both (and name Gl.* themselves, so importing render3d is enough), and r3d_present / r3d_screenshot are what a game calls. The Vulkan backend takes both over. OpenGL frames byte-identical at the five viewpoints; 59 self-tests pass with no GL error. Co-Authored-By: Claude Opus 5 --- packages/ludic.render3d/gpu.ludic | 4 ++++ packages/ludic.render3d/render.ludic | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/packages/ludic.render3d/gpu.ludic b/packages/ludic.render3d/gpu.ludic index 86793d84..9a5feefe 100644 --- a/packages/ludic.render3d/gpu.ludic +++ b/packages/ludic.render3d/gpu.ludic @@ -220,6 +220,10 @@ function gpu_open(w: int, h: int, title: string) -> bool { return gl_open(w, h, 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 } +# the finished frame: presented to the window, or (headless) the GPU's work finished +function gpu_present() -> void { Gl.swap() } +# the frame as it will be presented, to a binary PPM with the top row first; before gpu_present +function gpu_screenshot(path: string) -> bool { return Gl.screenshot(path: path) } var gpu_u_tmp: words = null function gpu_tmp() -> words { if gpu_u_tmp == null { gpu_u_tmp = words(4) }; return gpu_u_tmp } diff --git a/packages/ludic.render3d/render.ludic b/packages/ludic.render3d/render.ludic index 9dfe1fb8..a43b76f5 100644 --- a/packages/ludic.render3d/render.ludic +++ b/packages/ludic.render3d/render.ludic @@ -115,6 +115,11 @@ function r3d_draw_sky() -> void { gpu_depth_func(GL_LESS) } +# The end of the game's frame, whichever backend draws it: a screenshot first if one is wanted, +# then the present. A game calls these rather than Gl.swap / Gl.screenshot. +function r3d_screenshot(path: string) -> bool { return gpu_screenshot(path) } +function r3d_present() -> void { gpu_present() } + # the drawable changed size: the camera's aspect and every screen-sized target follow function r3d_resize() -> void { cam_aspect = fr(gl_w, gl_h)