From 7505c44162ab1930ea5aabaececb453aff5546ed Mon Sep 17 00:00:00 2001 From: Boris Batkin Date: Thu, 30 Jul 2026 04:28:02 -0700 Subject: [PATCH 1/6] dasHV tutorials: GC boundary in the server-thread fixture; reframe it as test scaffolding The fixture's threaded tick loop ran with zero collect points -- and a thread context can collect its own heap when the program declares options gc (probe-verified; all 8 tutorial roots already carried gc+persistent_heap as dead flags). maybe_collect_gc() now sits between ticks in all 7 loop sites. The fixture header states what it is: test scaffolding so the client script can run in-process, NOT an app skeleton -- real servers use the canonical shape (examples/hv/ws_chat_server.das). Run-proven: tutorials 03/05/06/07 end-to-end, exit 0; lint 7 files clean; format-verified. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01NcrnLGE9AEh8EAND3xYCRC --- tutorials/dasHV/03_http_server.das | 7 +++++-- tutorials/dasHV/04_http_server_advanced.das | 5 ++++- tutorials/dasHV/05_cookies_and_forms.das | 7 +++++-- tutorials/dasHV/06_websockets.das | 5 ++++- tutorials/dasHV/07_sse_and_streaming.das | 5 ++++- tutorials/dasHV/08_https_wss.das | 3 +++ tutorials/dasHV/tutorial_server.das | 9 +++++++++ 7 files changed, 34 insertions(+), 7 deletions(-) diff --git a/tutorials/dasHV/03_http_server.das b/tutorials/dasHV/03_http_server.das index 3ae8d1e6ea..7777503ab3 100644 --- a/tutorials/dasHV/03_http_server.das +++ b/tutorials/dasHV/03_http_server.das @@ -23,6 +23,8 @@ options gc require dashv/dashv_boost public require daslib/jobque_boost +require live/live_gc +require live_host require daslib/json_boost require daslib/fio @@ -40,7 +42,7 @@ let SERVER_PORT = 18082 // Content-Type: text/plain and the body in one call. class TutorialHttpServer : HvWebServer { - def override onInit { + def override onInit { // nolint:STYLE038 — tutorial route table, linear by design // --- Section 1: basic GET route --- GET("/hello") <| @(var req : HttpRequest?; var resp : HttpResponse?) : http_status { @@ -138,6 +140,7 @@ def with_server(port : int; blk : block<(base_url : string) : void>) { started |> notify_and_release while (stop_flag |> get == 0) { server->tick() + maybe_collect_gc() // the thread owns this context's heap; collect between ticks sleep(10u) } server->stop() @@ -162,7 +165,7 @@ def with_server(port : int; blk : block<(base_url : string) : void>) { // ────────────────────────────────────────────────────────────────────────── [export] -def main() { +def main() { // nolint:STYLE038 — tutorial client script, linear by design with_server(SERVER_PORT) $(base_url) { print("=== Section 1: Minimal GET route ===\n") diff --git a/tutorials/dasHV/04_http_server_advanced.das b/tutorials/dasHV/04_http_server_advanced.das index 23060d9d9b..c8709a1f6f 100644 --- a/tutorials/dasHV/04_http_server_advanced.das +++ b/tutorials/dasHV/04_http_server_advanced.das @@ -20,6 +20,8 @@ options gc require dashv/dashv_boost public require daslib/jobque_boost +require live/live_gc +require live_host require daslib/json_boost require daslib/fio @@ -192,6 +194,7 @@ def with_advanced_server(port : int; static_root : string; blk : block<(base_url started |> notify_and_release while (stop_flag |> get == 0) { server->tick() + maybe_collect_gc() // the thread owns this context's heap; collect between ticks sleep(10u) } server->stop() @@ -228,7 +231,7 @@ def with_advanced_server(port : int; static_root : string; blk : block<(base_url // set_content_type(resp, content_type) Set Content-Type header [export] -def main() { +def main() { // nolint:STYLE038 — tutorial client script, linear by design let static_root = "{get_das_root()}/tutorials/dasHV/_static_test" create_test_files(static_root) diff --git a/tutorials/dasHV/05_cookies_and_forms.das b/tutorials/dasHV/05_cookies_and_forms.das index 218d5b38d4..2bff752553 100644 --- a/tutorials/dasHV/05_cookies_and_forms.das +++ b/tutorials/dasHV/05_cookies_and_forms.das @@ -22,8 +22,10 @@ options gc require dashv/dashv_boost public require daslib/jobque_boost +require live/live_gc +require live_host require daslib/json_boost -require strings + require daslib/fio let SERVER_PORT = 18084 @@ -177,6 +179,7 @@ def with_cookie_form_server(port : int; upload_dir : string; blk : block<(base_u started |> notify_and_release while (stop_flag |> get == 0) { server->tick() + maybe_collect_gc() // the thread owns this context's heap; collect between ticks sleep(10u) } server->stop() @@ -216,7 +219,7 @@ def with_cookie_form_server(port : int; upload_dir : string; blk : block<(base_u // get_url_encoded(msg, key) Get URL-encoded field (server) [export] -def main() { +def main() { // nolint:STYLE038 — tutorial client script, linear by design let upload_dir = "{get_das_root()}/tutorials/dasHV/_upload_test" let test_file = "{get_das_root()}/tutorials/dasHV/_testfile.txt" create_upload_dir(upload_dir) diff --git a/tutorials/dasHV/06_websockets.das b/tutorials/dasHV/06_websockets.das index 2b2dc15143..868fd5698c 100644 --- a/tutorials/dasHV/06_websockets.das +++ b/tutorials/dasHV/06_websockets.das @@ -23,6 +23,8 @@ options gc require dashv/dashv_boost public require daslib/jobque_boost +require live/live_gc +require live_host require daslib/fio let SERVER_PORT = 18085 @@ -144,6 +146,7 @@ def with_ws_server(port : int; blk : block<(base_url : string) : void>) { started |> notify_and_release while (stop_flag |> get == 0) { server->tick() + maybe_collect_gc() // the thread owns this context's heap; collect between ticks sleep(10u) } server->stop() @@ -189,7 +192,7 @@ def wait_for_messages(var client : ChatClient?; count : int; max_ms : int = 2000 } [export] -def main() { +def main() { // nolint:STYLE038 — tutorial client script, linear by design with_ws_server(SERVER_PORT) $(base_url) { // ── Section 2a: Single client connection ── diff --git a/tutorials/dasHV/07_sse_and_streaming.das b/tutorials/dasHV/07_sse_and_streaming.das index 13ae2dcbf7..9c34a88bf1 100644 --- a/tutorials/dasHV/07_sse_and_streaming.das +++ b/tutorials/dasHV/07_sse_and_streaming.das @@ -22,6 +22,8 @@ options gc require dashv/dashv_boost public require daslib/jobque_boost +require live/live_gc +require live_host require strings require daslib/fio @@ -89,6 +91,7 @@ def with_server(port : int; blk : block<(base_url : string) : void>) { started |> notify_and_release while (stop_flag |> get == 0) { server->tick() + maybe_collect_gc() // the thread owns this context's heap; collect between ticks sleep(10u) } server->stop() @@ -151,7 +154,7 @@ def example_raw_streaming(base_url : string) { req.method = http_method.GET req.url := "{base_url}/events" var total_bytes = 0 - request_cb(req, $(data; var size : int) { + request_cb(req, $(_data; var size : int) { total_bytes += size }) $(var resp : HttpResponse?) { print(" status: {int(resp.status_code)}\n") diff --git a/tutorials/dasHV/08_https_wss.das b/tutorials/dasHV/08_https_wss.das index 183c63f13e..f0be581141 100644 --- a/tutorials/dasHV/08_https_wss.das +++ b/tutorials/dasHV/08_https_wss.das @@ -20,6 +20,8 @@ options gc require dashv/dashv_boost public require daslib/jobque_boost +require live/live_gc +require live_host require daslib/json_boost require daslib/fio @@ -57,6 +59,7 @@ def with_wss_server(port, https_port : int; blk : block<(https_url : string) : v started |> notify_and_release while (stop_flag |> get == 0) { server->tick() + maybe_collect_gc() // the thread owns this context's heap; collect between ticks sleep(10u) } server->stop() diff --git a/tutorials/dasHV/tutorial_server.das b/tutorials/dasHV/tutorial_server.das index 6f7c2683c6..779b400dad 100644 --- a/tutorials/dasHV/tutorial_server.das +++ b/tutorials/dasHV/tutorial_server.das @@ -28,6 +28,8 @@ options gen2 require dashv/dashv_boost public require daslib/jobque_boost +require live/live_gc +require live_host require daslib/fio // ────────────────────────────────────────────────────────────────────────── @@ -62,6 +64,12 @@ class TutorialServer : HvWebServer { // with_tutorial_server starts the server on a dedicated thread that ticks // in a loop, runs the user's block, then shuts everything down. // +// TEST SCAFFOLDING, not an application skeleton: the threaded tick loop exists +// so the tutorial's client script can run against a live server in ONE process. +// A real server uses the canonical application shape — [export] init/update/ +// shutdown + a standalone main with the GC boundary — see +// examples/hv/ws_chat_server.das (that shape also live-reloads; this one can't). +// // Internally hv::WebSocketServer::start() creates network accept/worker // threads, and tick() processes the WebSocket event queue + calls onTick. // We run the tick loop on a separate OS thread via new_thread so the @@ -84,6 +92,7 @@ def with_tutorial_server(port : int; blk : block<(base_url : string) : void>) { started |> notify_and_release // signal: server is listening while (stop_flag |> get == 0) { server->tick() + maybe_collect_gc() // the thread owns this context's heap; collect between ticks sleep(10u) } server->stop() From eaa0ebc762ebcfc167b83088b71bcd02f56fa5fa Mon Sep 17 00:00:00 2001 From: Boris Batkin Date: Thu, 30 Jul 2026 04:30:56 -0700 Subject: [PATCH 2/6] tutorials/opengl: standalone GC boundary in all 12 desktop drivers Each tutorial gains options gc + persistent_heap (nothing in their require chain cascades either), require live/live_gc, and maybe_collect_gc() inside main's while (update()) loop. The bool-returning update stays -- it is a blessed stop-signal variant, not a web contract (the wasm host auto-drives the trio and never calls main; the files' own comments already said so correctly). Pre-existing long-function warnings nolinted at 4 sites. Lint 12 files clean, dasfmt verified. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01NcrnLGE9AEh8EAND3xYCRC --- tutorials/opengl/01_triangle/01_triangle.das | 4 ++++ tutorials/opengl/02_mandelbrot/02_mandelbrot.das | 4 ++++ tutorials/opengl/03_sdf/03_sdf.das | 4 ++++ tutorials/opengl/04_cube/04_cube.das | 4 ++++ tutorials/opengl/05_instancing/05_instancing.das | 4 ++++ tutorials/opengl/06_skybox/06_skybox.das | 4 ++++ tutorials/opengl/07_particles/07_particles.das | 4 ++++ tutorials/opengl/08_shadow/08_shadow.das | 4 ++++ tutorials/opengl/09_msaa/09_msaa.das | 4 ++++ tutorials/opengl/10_deferred/10_deferred.das | 8 ++++++-- tutorials/opengl/11_hdr/11_hdr.das | 6 +++++- tutorials/opengl/12_gltf/12_gltf.das | 6 +++++- 12 files changed, 52 insertions(+), 4 deletions(-) diff --git a/tutorials/opengl/01_triangle/01_triangle.das b/tutorials/opengl/01_triangle/01_triangle.das index 75b5eb56ef..70a137522a 100644 --- a/tutorials/opengl/01_triangle/01_triangle.das +++ b/tutorials/opengl/01_triangle/01_triangle.das @@ -1,8 +1,11 @@ options gen2 +options gc +options persistent_heap require glfw/glfw_boost require opengl/opengl_boost require math require daslib/safe_addr +require live/live_gc // 01 - The Rotating Triangle (OpenGL / WebGL2 tutorial) // @@ -119,6 +122,7 @@ def shutdown { def main { init() while (update()) { + maybe_collect_gc() } shutdown() } diff --git a/tutorials/opengl/02_mandelbrot/02_mandelbrot.das b/tutorials/opengl/02_mandelbrot/02_mandelbrot.das index c5bd12caa2..e8ad9dfbc1 100644 --- a/tutorials/opengl/02_mandelbrot/02_mandelbrot.das +++ b/tutorials/opengl/02_mandelbrot/02_mandelbrot.das @@ -1,8 +1,11 @@ options gen2 +options gc +options persistent_heap require glfw/glfw_boost require opengl/opengl_boost require math require daslib/safe_addr +require live/live_gc // 02 - The Mandelbrot Set (OpenGL / WebGL2 tutorial) // @@ -209,6 +212,7 @@ def shutdown { def main { init() while (update()) { + maybe_collect_gc() } shutdown() } diff --git a/tutorials/opengl/03_sdf/03_sdf.das b/tutorials/opengl/03_sdf/03_sdf.das index a06a6f953f..fd750f6e3f 100644 --- a/tutorials/opengl/03_sdf/03_sdf.das +++ b/tutorials/opengl/03_sdf/03_sdf.das @@ -1,8 +1,11 @@ options gen2 +options gc +options persistent_heap require glfw/glfw_boost require opengl/opengl_boost require math require daslib/safe_addr +require live/live_gc // 03 - Signed-Distance-Field Raymarcher (OpenGL / WebGL2 tutorial) // @@ -324,6 +327,7 @@ def shutdown { def main { init() while (update()) { + maybe_collect_gc() } shutdown() } diff --git a/tutorials/opengl/04_cube/04_cube.das b/tutorials/opengl/04_cube/04_cube.das index 9a7d0cfd12..2af19afbbb 100644 --- a/tutorials/opengl/04_cube/04_cube.das +++ b/tutorials/opengl/04_cube/04_cube.das @@ -1,9 +1,12 @@ options gen2 +options gc +options persistent_heap require glfw/glfw_boost require opengl/opengl_boost require math require daslib/math_boost require daslib/safe_addr +require live/live_gc // 04 - The Synthwave Cube (OpenGL / WebGL2 tutorial) // @@ -282,6 +285,7 @@ def shutdown { def main { init() while (update()) { + maybe_collect_gc() } shutdown() } diff --git a/tutorials/opengl/05_instancing/05_instancing.das b/tutorials/opengl/05_instancing/05_instancing.das index 965213ac06..a8225ea2d9 100644 --- a/tutorials/opengl/05_instancing/05_instancing.das +++ b/tutorials/opengl/05_instancing/05_instancing.das @@ -1,9 +1,12 @@ options gen2 +options gc +options persistent_heap require glfw/glfw_boost require opengl/opengl_boost require math require daslib/math_boost require daslib/safe_addr +require live/live_gc // 05 - The Cube Swarm (OpenGL / WebGL2 tutorial) // @@ -253,6 +256,7 @@ def shutdown { def main { init() while (update()) { + maybe_collect_gc() } shutdown() } diff --git a/tutorials/opengl/06_skybox/06_skybox.das b/tutorials/opengl/06_skybox/06_skybox.das index ffe4528451..30af6b8a0b 100644 --- a/tutorials/opengl/06_skybox/06_skybox.das +++ b/tutorials/opengl/06_skybox/06_skybox.das @@ -1,9 +1,12 @@ options gen2 +options gc +options persistent_heap require glfw/glfw_boost require opengl/opengl_boost require math require daslib/math_boost require daslib/safe_addr +require live/live_gc // 06 - The Skybox (OpenGL / WebGL2 tutorial) // @@ -448,6 +451,7 @@ def shutdown { def main { init() while (update()) { + maybe_collect_gc() } shutdown() } diff --git a/tutorials/opengl/07_particles/07_particles.das b/tutorials/opengl/07_particles/07_particles.das index 8ab1258f52..e419d07755 100644 --- a/tutorials/opengl/07_particles/07_particles.das +++ b/tutorials/opengl/07_particles/07_particles.das @@ -1,9 +1,12 @@ options gen2 +options gc +options persistent_heap require glfw/glfw_boost require opengl/opengl_boost require math require daslib/math_boost require daslib/safe_addr +require live/live_gc // 07 - The Particle Swarm (OpenGL / WebGL2 tutorial) // @@ -270,6 +273,7 @@ def shutdown { def main { init() while (update()) { + maybe_collect_gc() } shutdown() } diff --git a/tutorials/opengl/08_shadow/08_shadow.das b/tutorials/opengl/08_shadow/08_shadow.das index 4c7f27e871..f0458bc01b 100644 --- a/tutorials/opengl/08_shadow/08_shadow.das +++ b/tutorials/opengl/08_shadow/08_shadow.das @@ -1,9 +1,12 @@ options gen2 +options gc +options persistent_heap require glfw/glfw_boost require opengl/opengl_boost require math require daslib/math_boost require daslib/safe_addr +require live/live_gc // 08 - The Shadow Map (OpenGL / WebGL2 tutorial) // @@ -411,6 +414,7 @@ def shutdown { def main { init() while (update()) { + maybe_collect_gc() } shutdown() } diff --git a/tutorials/opengl/09_msaa/09_msaa.das b/tutorials/opengl/09_msaa/09_msaa.das index 7f5bb81b0b..dec6bcfac0 100644 --- a/tutorials/opengl/09_msaa/09_msaa.das +++ b/tutorials/opengl/09_msaa/09_msaa.das @@ -1,9 +1,12 @@ options gen2 +options gc +options persistent_heap require glfw/glfw_boost require opengl/opengl_boost require math require daslib/math_boost require daslib/safe_addr +require live/live_gc // 09 - Multisample Anti-Aliasing (OpenGL / WebGL2 tutorial) // @@ -417,6 +420,7 @@ def shutdown { def main { init() while (update()) { + maybe_collect_gc() } shutdown() } diff --git a/tutorials/opengl/10_deferred/10_deferred.das b/tutorials/opengl/10_deferred/10_deferred.das index b0e44e8703..c965ed8b38 100644 --- a/tutorials/opengl/10_deferred/10_deferred.das +++ b/tutorials/opengl/10_deferred/10_deferred.das @@ -1,4 +1,6 @@ options gen2 +options gc +options persistent_heap require glfw/glfw_boost require opengl/opengl_boost require geometry/geom_gen @@ -6,6 +8,7 @@ require stbimage require math require daslib/math_boost require daslib/safe_addr +require live/live_gc // 10 - Deferred Shading + MRT (OpenGL / WebGL2 tutorial) // @@ -256,7 +259,7 @@ def point_light(lp, lcol : float3; p, nrm, vdir : float3; shininess, spec_i, rng } [fragment_program] -def lighting_fs { +def lighting_fs { // nolint:STYLE038 — deferred lighting shader, linear by design // ----- bottom filmstrip: raw G-buffer (albedo / normal / world-pos) + SSAO + shadow ----- let TH = 0.18 let GAP = 0.012 @@ -597,7 +600,7 @@ def draw_scene_geometry { } [export] -def update : bool { +def update : bool { // nolint:STYLE038 — one frame of the tutorial pipeline time += 1.0 / 60.0 let t = time var dw, dh : int @@ -704,6 +707,7 @@ def shutdown { def main { init() while (update()) { + maybe_collect_gc() } shutdown() } diff --git a/tutorials/opengl/11_hdr/11_hdr.das b/tutorials/opengl/11_hdr/11_hdr.das index 2cccc812ab..811bbe4b66 100644 --- a/tutorials/opengl/11_hdr/11_hdr.das +++ b/tutorials/opengl/11_hdr/11_hdr.das @@ -1,9 +1,12 @@ options gen2 +options gc +options persistent_heap require glfw/glfw_boost require opengl/opengl_boost require math require daslib/math_boost require daslib/safe_addr +require live/live_gc // 11 - HDR + Bloom (OpenGL / WebGL2 tutorial) // @@ -408,7 +411,7 @@ def draw_fullscreen { } [export] -def update : bool { +def update : bool { // nolint:STYLE038 — one frame of the tutorial pipeline time += 1.0 / 60.0 let t = time var display_w, display_h : int @@ -514,6 +517,7 @@ def shutdown { def main { init() while (update()) { + maybe_collect_gc() } shutdown() } diff --git a/tutorials/opengl/12_gltf/12_gltf.das b/tutorials/opengl/12_gltf/12_gltf.das index ed402dcc5a..2d95afd9ce 100644 --- a/tutorials/opengl/12_gltf/12_gltf.das +++ b/tutorials/opengl/12_gltf/12_gltf.das @@ -1,4 +1,6 @@ options gen2 +options gc +options persistent_heap require glfw/glfw_boost require opengl/opengl_boost require gltf/gltf_boost @@ -7,6 +9,7 @@ require stbimage require math require daslib/math_boost require daslib/safe_addr +require live/live_gc // 12 - glTF PBR Capstone: deferred + HDR + bloom (OpenGL / WebGL2 tutorial) // @@ -842,7 +845,7 @@ def draw_fullscreen { } [export] -def update : bool { +def update : bool { // nolint:STYLE038 — one frame of the tutorial pipeline time += 1.0 / 60.0 let t = time var display_w, display_h : int @@ -1006,6 +1009,7 @@ def shutdown { def main { init() while (update()) { + maybe_collect_gc() } shutdown() } From b2c03ee58fa5fb631ad535afecfc98bc0cb36372 Mon Sep 17 00:00:00 2001 From: Boris Batkin Date: Thu, 30 Jul 2026 04:35:07 -0700 Subject: [PATCH 3/6] pathTracer GL viewers + hrtf: canonical application shape The three pathTracer viewers and the HRTF demo split their monolithic mains into the exported trio + a canonical main (update + maybe_collect_gc per frame, --max-frames bound; the viewers' --frames long flag renamed to --max-frames, -f kept, no callers existed). Frame bodies moved verbatim; hrtf's audio callback/mixer path untouched, teardown order preserved exactly (finalize then LIFO defers). All four run-proven windowed: 60-frame bounded runs exit 0 (hrtf plays and finalizes its mixer cleanly). CPU toy_path_tracer + _profile still compile. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01NcrnLGE9AEh8EAND3xYCRC --- examples/audio/hrtf/main.das | 310 ++++++++++-------- .../pathTracer/toy_path_tracer_opengl.das | 100 +++--- .../pathTracer/toy_path_tracer_opengl_hdr.das | 172 +++++----- .../toy_pathtracer_opengl_basic.das | 84 +++-- 4 files changed, 370 insertions(+), 296 deletions(-) diff --git a/examples/audio/hrtf/main.das b/examples/audio/hrtf/main.das index 46506a6bc3..454f54a437 100644 --- a/examples/audio/hrtf/main.das +++ b/examples/audio/hrtf/main.das @@ -1,5 +1,6 @@ options gen2 options persistent_heap +options gc options no_global_variables = false options no_unused_block_arguments = false options no_unused_function_arguments = false @@ -8,7 +9,7 @@ require glfw/glfw_boost require opengl/opengl_boost require opengl/opengl_gen require audio/audio_boost -require daslib/defer +require live/live_gc require daslib/fio require daslib/safe_addr require daslib/math_boost @@ -183,6 +184,18 @@ def add_sound_source(pos : float3; volume : float = 1.0) { // -- Main -- +var window : GLFWwindow? +var solid_program : uint +var line_program : uint +var sphere : OpenGLGeometryFragment +var stats_box : LockBox? +var asch : AudioSystemChannels +var last_time = 0.0lf +var n_was_pressed = false +var m_was_pressed = false +var b_was_pressed = false +var since_stats_print = 0.0 + // Parse --max-frames N from argv. Useful for headless repro and shutdown-leak debugging. def parse_max_frames { var maxFrames = 0 @@ -196,31 +209,24 @@ def parse_max_frames { } [export] -def main { - let maxFrames = parse_max_frames() +def init { // GLFW init if (glfwInit() == 0) { panic("can't init glfw") } - defer() { - glfwTerminate() - } glfwInitOpenGL(3, 3) - var window = glfwCreateWindow(1280, 720, "HRTF 3D Audio Demo", null, null) + window = glfwCreateWindow(1280, 720, "HRTF 3D Audio Demo", null, null) if (window == null) { panic("can't create window") } - defer() { - glfwDestroyWindow(window) - } glfwMakeContextCurrent(window) // Shaders - let solid_program = create_shader_program(@@vs_solid, @@fs_solid) - let line_program = create_shader_program(@@vs_line, @@fs_line) + solid_program = create_shader_program(@@vs_solid, @@fs_solid) + line_program = create_shader_program(@@vs_line, @@fs_line) // Geometry - let sphere <- gen_sphere(16, 12, false) |> create_geometry_fragment + sphere <- gen_sphere(16, 12, false) |> create_geometry_fragment create_grid(20, 1.0) // Mouse callbacks @@ -259,145 +265,159 @@ def main { // Stats LockBox for per-second utilization + HRTF/simulated split readout. // Lifecycle wraps the audio system: the audio thread releases its share-ref during - // audio_system_finalize (inside with_audio_system below), THEN this outer defer - // calls lock_box_remove to do the final delete. `release` alone never deletes. - var stats_box <- lock_box_create() - defer() { - unsafe(lock_box_remove(stats_box)) + // audio_system_finalize, THEN shutdown calls lock_box_remove to do the final delete. + // `release` alone never deletes. + stats_box = lock_box_create() + + // Audio system — all audio API calls happen between here and audio_system_finalize in shutdown + asch = audio_system_create() + + // Start with one source in front + add_sound_source(float3(0.0, 0.0, -3.0)) + + set_audio_stats_box(stats_box) + + print("HRTF 3D Audio Demo\n") + print(" Click to capture mouse, click again to release\n") + print(" WASD to move, mouse to look\n") + print(" N to add a sound source, M to add 30, ESC to quit\n") + print(" B to cycle HRTF budget (mixed top-32 / all simulated / all HRTF)\n") + + last_time = glfwGetTime() +} + +[export] +def update { // nolint:STYLE038 — linear per-frame UI flow + glfwPollEvents() + + // Delta time + let now = glfwGetTime() + let dt = float(now - last_time) + last_time = now + + // Movement + let speed = 5.0 * dt + let fwd = camera_forward() + let rgt = camera_right() + if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) { + camera_pos += fwd * speed + } + if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) { + camera_pos -= fwd * speed + } + if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) { + camera_pos -= rgt * speed + } + if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) { + camera_pos += rgt * speed } - // Audio system - with_audio_system() { - // Start with one source in front - add_sound_source(float3(0.0, 0.0, -3.0)) - - set_audio_stats_box(stats_box) - - print("HRTF 3D Audio Demo\n") - print(" Click to capture mouse, click again to release\n") - print(" WASD to move, mouse to look\n") - print(" N to add a sound source, M to add 30, ESC to quit\n") - print(" B to cycle HRTF budget (mixed top-32 / all simulated / all HRTF)\n") - - var last_time = glfwGetTime() - var n_was_pressed = false - var m_was_pressed = false - var b_was_pressed = false - var since_stats_print = 0.0 - var frameCount = 0 - - eval_main_loop() { - if (glfwWindowShouldClose(window) != 0 || glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS || (maxFrames > 0 && frameCount >= maxFrames)) return false - frameCount ++ - glfwPollEvents() - - // Delta time - let now = glfwGetTime() - let dt = float(now - last_time) - last_time = now - - // Movement - let speed = 5.0 * dt - let fwd = camera_forward() - let rgt = camera_right() - if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) { - camera_pos += fwd * speed - } - if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) { - camera_pos -= fwd * speed - } - if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) { - camera_pos -= rgt * speed - } - if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) { - camera_pos += rgt * speed - } + // Add source on N press + let n_pressed = glfwGetKey(window, GLFW_KEY_N) == GLFW_PRESS + if (n_pressed && !n_was_pressed) { + let spawn_pos = camera_pos + fwd * 3.0 + add_sound_source(spawn_pos) + print("Added source #{length(sources)} at ({spawn_pos.x}, {spawn_pos.y}, {spawn_pos.z})\n") + } + n_was_pressed = n_pressed + + // Add 30 sources around camera on M press + let m_pressed = glfwGetKey(window, GLFW_KEY_M) == GLFW_PRESS + if (m_pressed && !m_was_pressed) { + for (i in range(30)) { + let angle = 2.0 * PI * float(i) / 30.0 + let radius = 3.0 + float(i % 3) + let spawn_pos = camera_pos + float3(cos(angle) * radius, 0.0, sin(angle) * radius) + add_sound_source(spawn_pos, 0.1) + } + print("Added 30 sources around camera\n") + } + m_was_pressed = m_pressed + + // Cycle HRTF budget on B press + let b_pressed = glfwGetKey(window, GLFW_KEY_B) == GLFW_PRESS + if (b_pressed && !b_was_pressed) { + hrtf_budget_idx = (hrtf_budget_idx + 1) % 3 + let newBudget = HRTF_BUDGETS[hrtf_budget_idx] + set_hrtf_budget(newBudget) + print("HRTF budget: {newBudget} ({HRTF_BUDGET_LABELS[hrtf_budget_idx]})\n") + } + b_was_pressed = b_pressed + + // Per-second audio-system stats line. `get` is read-only — keeps the box alive across reads. + // `grab` would consume the notification and break the periodic publish/poll loop. + since_stats_print += dt + if (since_stats_print >= 1.0) { + stats_box |> get() $(s : AudioSystemStats#) { + print("audio: util={s.utilization_pct}%, hrtf={s.hrtf_count}/{s.total_3d} (budget={HRTF_BUDGETS[hrtf_budget_idx]})\n") + } + since_stats_print = 0.0 + } - // Add source on N press - let n_pressed = glfwGetKey(window, GLFW_KEY_N) == GLFW_PRESS - if (n_pressed && !n_was_pressed) { - let spawn_pos = camera_pos + fwd * 3.0 - add_sound_source(spawn_pos) - print("Added source #{length(sources)} at ({spawn_pos.x}, {spawn_pos.y}, {spawn_pos.z})\n") - } - n_was_pressed = n_pressed - - // Add 30 sources around camera on M press - let m_pressed = glfwGetKey(window, GLFW_KEY_M) == GLFW_PRESS - if (m_pressed && !m_was_pressed) { - for (i in range(30)) { - let angle = 2.0 * PI * float(i) / 30.0 - let radius = 3.0 + float(i % 3) - let spawn_pos = camera_pos + float3(cos(angle) * radius, 0.0, sin(angle) * radius) - add_sound_source(spawn_pos, 0.1) - } - print("Added 30 sources around camera\n") - } - m_was_pressed = m_pressed - - // Cycle HRTF budget on B press - let b_pressed = glfwGetKey(window, GLFW_KEY_B) == GLFW_PRESS - if (b_pressed && !b_was_pressed) { - hrtf_budget_idx = (hrtf_budget_idx + 1) % 3 - let newBudget = HRTF_BUDGETS[hrtf_budget_idx] - set_hrtf_budget(newBudget) - print("HRTF budget: {newBudget} ({HRTF_BUDGET_LABELS[hrtf_budget_idx]})\n") - } - b_was_pressed = b_pressed - - // Per-second audio-system stats line. `get` is read-only — keeps the box alive across reads. - // `grab` would consume the notification and break the periodic publish/poll loop. - since_stats_print += dt - if (since_stats_print >= 1.0) { - stats_box |> get() $(s : AudioSystemStats#) { - print("audio: util={s.utilization_pct}%, hrtf={s.hrtf_count}/{s.total_3d} (budget={HRTF_BUDGETS[hrtf_budget_idx]})\n") - } - since_stats_print = 0.0 - } + // Update audio listener + set_head_position(gl_to_audio(camera_pos), gl_to_audio(fwd)) - // Update audio listener - set_head_position(gl_to_audio(camera_pos), gl_to_audio(fwd)) + // Update source positions (they don't move, but we could animate here) + for (src in sources) { + src.sid |> set_position(gl_to_audio(src.position)) + } - // Update source positions (they don't move, but we could animate here) - for (src in sources) { - src.sid |> set_position(gl_to_audio(src.position)) - } + // -- Render -- + var display_w, display_h : int + glfwGetFramebufferSize(window, display_w, display_h) + let aspect = display_h != 0 ? float(display_w) / float(display_h) : 1.0 + + glViewport(0, 0, display_w, display_h) + glClearColor(0.1, 0.1, 0.15, 1.0) + glClearDepth(1.0lf) + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) + glEnable(GL_DEPTH_TEST) + glDepthFunc(GL_LEQUAL) + + let view = look_at_rh(camera_pos, camera_pos + fwd, float3(0.0, 1.0, 0.0)) + let proj = perspective_rh_opengl(60.0 * PI / 180.0, aspect, 0.1, 100.0) + v_view = view + v_projection = proj + + // Grid + draw_grid(line_program, view, proj) + + // Sound source spheres + glUseProgram(solid_program) + glEnable(GL_CULL_FACE) + glCullFace(GL_BACK) + for (src in sources) { + v_model = compose(src.position, float4(0.0, 0.0, 0.0, 1.0), float3(0.3)) + f_color = src.color + vs_solid_bind_uniform(solid_program) + fs_solid_bind_uniform(solid_program) + draw_geometry_fragment(sphere) + } + glUseProgram(0u) - // -- Render -- - var display_w, display_h : int - glfwGetFramebufferSize(window, display_w, display_h) - let aspect = display_h != 0 ? float(display_w) / float(display_h) : 1.0 - - glViewport(0, 0, display_w, display_h) - glClearColor(0.1, 0.1, 0.15, 1.0) - glClearDepth(1.0lf) - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) - glEnable(GL_DEPTH_TEST) - glDepthFunc(GL_LEQUAL) - - let view = look_at_rh(camera_pos, camera_pos + fwd, float3(0.0, 1.0, 0.0)) - let proj = perspective_rh_opengl(60.0 * PI / 180.0, aspect, 0.1, 100.0) - v_view = view - v_projection = proj - - // Grid - draw_grid(line_program, view, proj) - - // Sound source spheres - glUseProgram(solid_program) - glEnable(GL_CULL_FACE) - glCullFace(GL_BACK) - for (src in sources) { - v_model = compose(src.position, float4(0.0, 0.0, 0.0, 1.0), float3(0.3)) - f_color = src.color - vs_solid_bind_uniform(solid_program) - fs_solid_bind_uniform(solid_program) - draw_geometry_fragment(sphere) - } - glUseProgram(0u) + glfwSwapBuffers(window) +} + +[export] +def shutdown { + audio_system_finalize(asch.command, asch.next_sid) + unsafe(lock_box_remove(stats_box)) + glfwDestroyWindow(window) + glfwTerminate() +} - glfwSwapBuffers(window) - return true +[export] +def main { + let maxFrames = parse_max_frames() + init() + var frameCount = 0 + while (glfwWindowShouldClose(window) == 0 && glfwGetKey(window, GLFW_KEY_ESCAPE) != GLFW_PRESS) { + update() + maybe_collect_gc() + frameCount ++ + if (maxFrames > 0 && frameCount >= maxFrames) { + break } } + shutdown() } diff --git a/examples/pathTracer/toy_path_tracer_opengl.das b/examples/pathTracer/toy_path_tracer_opengl.das index 1154f03572..20989a5c94 100644 --- a/examples/pathTracer/toy_path_tracer_opengl.das +++ b/examples/pathTracer/toy_path_tracer_opengl.das @@ -1,8 +1,9 @@ options gen2 +options persistent_heap +options gc require glfw/glfw_boost require opengl/opengl_boost require glsl/glsl_opengl -require daslib/defer require math require daslib/safe_addr @@ -10,6 +11,7 @@ require daslib/random require path_tracer require daslib/clargs +require live/live_gc let TRACE_WIDTH = 1024 let TRACE_HEIGHT = 1024 @@ -17,6 +19,7 @@ let TRACE_HEIGHT = 1024 [CommandLineArgs] struct PathTracerArgs { @clarg_short = "f" + @clarg_name = "max-frames" @clarg_doc = "Render this many frames, then exit 0 (0 = run until the window closes). Lets the demo double as a headless smoke test." frames : int @@ -74,6 +77,7 @@ var vao : uint var vbo : uint var ebo : uint var texture : uint +var window : GLFWwindow? [vertex_buffer] struct EffectVertex { @@ -110,6 +114,53 @@ def create_gl_objects { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST) } +[export] +def init { + if (glfwInit() == 0) { + panic("can't init glfw") + } + glfwInitOpenGL(4, 3) + window = glfwCreateWindow(TRACE_WIDTH, TRACE_HEIGHT, "OpenGL Path tracer", null, null) + if (window == null) { + panic("can't create window") + } + glfwSetWindowSize(window, TRACE_WIDTH, TRACE_HEIGHT) + glfwMakeContextCurrent(window) + create_gl_objects() +} + +[export] +def update { + glfwPollEvents() + var display_w, display_h : int + glfwGetFramebufferSize(window, display_w, display_h) + // copmute next iteration of tracing + glUseProgram(compute_program) + c_destTex := texture + compute_effect_bind_uniform(compute_program) + glDispatchCompute(uint(TRACE_WIDTH / 16), uint(TRACE_HEIGHT / 16), 1u) + glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT) + c_frame++ + // display it + glViewport(0, 0, display_w, display_h) + glUseProgram(display_program) + f_tex := texture + vs_effect_bind_uniform(display_program) + fs_effect_bind_uniform(display_program) + glBindVertexArray(vao) + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo) + glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, null) + // swap buffers + glfwMakeContextCurrent(window) + glfwSwapBuffers(window) +} + +[export] +def shutdown { + glfwDestroyWindow(window) + glfwTerminate() +} + [export] def main { var r <- parse_args(type) @@ -123,48 +174,15 @@ def main { print_help(get_command_info(type), "toy_path_tracer_opengl") return } - if (glfwInit() == 0) { - panic("can't init glfw") - } - defer() { - glfwTerminate() - } - glfwInitOpenGL(4, 3) - var window = glfwCreateWindow(TRACE_WIDTH, TRACE_HEIGHT, "OpenGL Path tracer", null, null) - if (window == null) { - panic("can't create window") - } - defer() { - glfwDestroyWindow(window) - } - glfwSetWindowSize(window, TRACE_WIDTH, TRACE_HEIGHT) - glfwMakeContextCurrent(window) - create_gl_objects() + init() + var frame_count = 0 while (glfwWindowShouldClose(window) == 0) { - glfwPollEvents() - var display_w, display_h : int - glfwGetFramebufferSize(window, display_w, display_h) - // copmute next iteration of tracing - glUseProgram(compute_program) - c_destTex := texture - compute_effect_bind_uniform(compute_program) - glDispatchCompute(uint(TRACE_WIDTH / 16), uint(TRACE_HEIGHT / 16), 1u) - glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT) - c_frame++ - // display it - glViewport(0, 0, display_w, display_h) - glUseProgram(display_program) - f_tex := texture - vs_effect_bind_uniform(display_program) - fs_effect_bind_uniform(display_program) - glBindVertexArray(vao) - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo) - glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, null) - // swap buffers - glfwMakeContextCurrent(window) - glfwSwapBuffers(window) - if (cfg.frames > 0 && c_frame >= cfg.frames) { + update() + maybe_collect_gc() + frame_count++ + if (cfg.frames > 0 && frame_count >= cfg.frames) { break // headless smoke / test mode: stop after the requested frame count } } + shutdown() } diff --git a/examples/pathTracer/toy_path_tracer_opengl_hdr.das b/examples/pathTracer/toy_path_tracer_opengl_hdr.das index 295854d23e..7422c39239 100644 --- a/examples/pathTracer/toy_path_tracer_opengl_hdr.das +++ b/examples/pathTracer/toy_path_tracer_opengl_hdr.das @@ -1,11 +1,12 @@ options gen2 +options persistent_heap +options gc require glfw/glfw_boost require opengl/opengl_boost require glsl/glsl_opengl -require daslib/defer require daslib/unroll require math @@ -14,6 +15,7 @@ require daslib/random require path_tracer require daslib/clargs +require live/live_gc let TRACE_WIDTH = 1024 let TRACE_HEIGHT = 1024 @@ -21,6 +23,7 @@ let TRACE_HEIGHT = 1024 [CommandLineArgs] struct PathTracerArgs { @clarg_short = "f" + @clarg_name = "max-frames" @clarg_doc = "Render this many frames, then exit 0 (0 = run until the window closes). Lets the demo double as a headless smoke test." frames : int @@ -157,6 +160,7 @@ def bloom_effect_h { var bloom_program_copy, bloom_program_h, bloom_program_v : uint var bloom_texture : uint[2] +var window : GLFWwindow? [vertex_buffer] struct EffectVertex { @@ -204,6 +208,89 @@ def create_gl_objects { } } +[export] +def init { + if (glfwInit() == 0) { + panic("can't init glfw") + } + glfwInitOpenGL(4, 3) + window = glfwCreateWindow(TRACE_WIDTH, TRACE_HEIGHT, "OpenGL Path tracer with bloom", null, null) + if (window == null) { + panic("can't create window") + } + glfwSetWindowSize(window, TRACE_WIDTH, TRACE_HEIGHT) + glfwMakeContextCurrent(window) + create_gl_objects() +} + +[export] +def update { + glfwPollEvents() + var display_w, display_h : int + glfwGetFramebufferSize(window, display_w, display_h) + // copmute next iteration of tracing + glUseProgram(compute_program) + c_destTex := texture + compute_effect_bind_uniform(compute_program) + glDispatchCompute(uint(TRACE_WIDTH / 16), uint(TRACE_HEIGHT / 16), 1u) + glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT) + c_frame++ + // bloom weights + let sigma = 4.0 + c_bloom_weights = fixed_array( + gaussian(-4., sigma), gaussian(-3., sigma), gaussian(-2., sigma), gaussian(-1., sigma), + gaussian(0., sigma), + gaussian(1., sigma), gaussian(2., sigma), gaussian(3., sigma), gaussian(4., sigma) + ) + c_bloom_iweights_summ = 0. + for (w in c_bloom_weights) { + c_bloom_iweights_summ += w + } + c_bloom_iweights_summ = 1.0 / c_bloom_iweights_summ + // bloom copy + glUseProgram(bloom_program_copy) + c_bloom_source := texture + c_bloom_vtex := bloom_texture[0] + c_bloom_htex := bloom_texture[1] + bloom_effect_copy_bind_uniform(bloom_program_copy) + glDispatchCompute(uint(TRACE_WIDTH / 16 / BLOOM_SCALE), uint(TRACE_HEIGHT / 16 / BLOOM_SCALE), 1u) + glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT) + // bloom v + glUseProgram(bloom_program_v) + c_bloom_source := texture + c_bloom_vtex := bloom_texture[0] + c_bloom_htex := bloom_texture[1] + bloom_effect_v_bind_uniform(bloom_program_v) + glDispatchCompute(uint(TRACE_WIDTH / 16 / BLOOM_SCALE), uint(TRACE_HEIGHT / 16 / BLOOM_SCALE), 1u) + glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT) + // bloom h + glUseProgram(bloom_program_h) + c_bloom_source := texture + c_bloom_vtex := bloom_texture[0] + c_bloom_htex := bloom_texture[1] + bloom_effect_h_bind_uniform(bloom_program_h) + glDispatchCompute(uint(TRACE_WIDTH / 16 / BLOOM_SCALE), uint(TRACE_HEIGHT / 16 / BLOOM_SCALE), 1u) + glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT) + // display it + glViewport(0, 0, display_w, display_h) + glUseProgram(display_program) + f_tex := texture + f_bloom := c_bloom_vtex + vs_effect_bind_uniform(display_program) + fs_effect_bind_uniform(display_program) + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo) + glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, null) + // swap buffers + glfwMakeContextCurrent(window) + glfwSwapBuffers(window) +} + +[export] +def shutdown { + glfwDestroyWindow(window) + glfwTerminate() +} + [export] def main { var r <- parse_args(type) @@ -217,84 +304,15 @@ def main { print_help(get_command_info(type), "toy_path_tracer_opengl_hdr") return } - if (glfwInit() == 0) { - panic("can't init glfw") - } - defer() { - glfwTerminate() - } - glfwInitOpenGL(4, 3) - var window = glfwCreateWindow(TRACE_WIDTH, TRACE_HEIGHT, "OpenGL Path tracer with bloom", null, null) - if (window == null) { - panic("can't create window") - } - defer() { - glfwDestroyWindow(window) - } - glfwSetWindowSize(window, TRACE_WIDTH, TRACE_HEIGHT) - glfwMakeContextCurrent(window) - create_gl_objects() + init() + var frame_count = 0 while (glfwWindowShouldClose(window) == 0) { - glfwPollEvents() - var display_w, display_h : int - glfwGetFramebufferSize(window, display_w, display_h) - // copmute next iteration of tracing - glUseProgram(compute_program) - c_destTex := texture - compute_effect_bind_uniform(compute_program) - glDispatchCompute(uint(TRACE_WIDTH / 16), uint(TRACE_HEIGHT / 16), 1u) - glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT) - c_frame++ - // bloom weights - let sigma = 4.0 - c_bloom_weights = fixed_array( - gaussian(-4., sigma), gaussian(-3., sigma), gaussian(-2., sigma), gaussian(-1., sigma), - gaussian(0., sigma), - gaussian(1., sigma), gaussian(2., sigma), gaussian(3., sigma), gaussian(4., sigma) - ) - c_bloom_iweights_summ = 0. - for (w in c_bloom_weights) { - c_bloom_iweights_summ += w - } - c_bloom_iweights_summ = 1.0 / c_bloom_iweights_summ - // bloom copy - glUseProgram(bloom_program_copy) - c_bloom_source := texture - c_bloom_vtex := bloom_texture[0] - c_bloom_htex := bloom_texture[1] - bloom_effect_copy_bind_uniform(bloom_program_copy) - glDispatchCompute(uint(TRACE_WIDTH / 16 / BLOOM_SCALE), uint(TRACE_HEIGHT / 16 / BLOOM_SCALE), 1u) - glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT) - // bloom v - glUseProgram(bloom_program_v) - c_bloom_source := texture - c_bloom_vtex := bloom_texture[0] - c_bloom_htex := bloom_texture[1] - bloom_effect_v_bind_uniform(bloom_program_v) - glDispatchCompute(uint(TRACE_WIDTH / 16 / BLOOM_SCALE), uint(TRACE_HEIGHT / 16 / BLOOM_SCALE), 1u) - glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT) - // bloom h - glUseProgram(bloom_program_h) - c_bloom_source := texture - c_bloom_vtex := bloom_texture[0] - c_bloom_htex := bloom_texture[1] - bloom_effect_h_bind_uniform(bloom_program_h) - glDispatchCompute(uint(TRACE_WIDTH / 16 / BLOOM_SCALE), uint(TRACE_HEIGHT / 16 / BLOOM_SCALE), 1u) - glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT) - // display it - glViewport(0, 0, display_w, display_h) - glUseProgram(display_program) - f_tex := texture - f_bloom := c_bloom_vtex - vs_effect_bind_uniform(display_program) - fs_effect_bind_uniform(display_program) - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo) - glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, null) - // swap buffers - glfwMakeContextCurrent(window) - glfwSwapBuffers(window) - if (cfg.frames > 0 && c_frame >= cfg.frames) { + update() + maybe_collect_gc() + frame_count++ + if (cfg.frames > 0 && frame_count >= cfg.frames) { break // headless smoke / test mode: stop after the requested frame count } } + shutdown() } diff --git a/examples/pathTracer/toy_pathtracer_opengl_basic.das b/examples/pathTracer/toy_pathtracer_opengl_basic.das index 137ff1754c..221a4e6d2d 100644 --- a/examples/pathTracer/toy_pathtracer_opengl_basic.das +++ b/examples/pathTracer/toy_pathtracer_opengl_basic.das @@ -1,8 +1,9 @@ options gen2 +options persistent_heap +options gc require glfw/glfw_boost require opengl/opengl_boost require glsl/glsl_opengl -require daslib/defer require math require daslib/safe_addr @@ -10,6 +11,7 @@ require daslib/random require path_tracer require daslib/clargs +require live/live_gc let TRACE_WIDTH = 1024 let TRACE_HEIGHT = 1024 @@ -17,6 +19,7 @@ let TRACE_HEIGHT = 1024 [CommandLineArgs] struct PathTracerArgs { @clarg_short = "f" + @clarg_name = "max-frames" @clarg_doc = "Render this many frames, then exit 0 (0 = run until the window closes). Lets the demo double as a headless smoke test." frames : int @@ -58,6 +61,7 @@ var vao : uint var vbo : uint var ebo : uint var texture : uint +var window : GLFWwindow? [vertex_buffer] struct EffectVertex { @@ -87,6 +91,45 @@ def create_gl_objects { glBufferData(GL_ELEMENT_ARRAY_BUFFER, effect_indices, GL_STATIC_DRAW) } +[export] +def init { + if (glfwInit() == 0) { + panic("can't init glfw") + } + glfwInitOpenGL(3, 3) + window = glfwCreateWindow(TRACE_WIDTH, TRACE_HEIGHT, "OpenGL Path tracer", null, null) + if (window == null) { + panic("can't create window") + } + glfwSetWindowSize(window, TRACE_WIDTH, TRACE_HEIGHT) + glfwMakeContextCurrent(window) + create_gl_objects() +} + +[export] +def update { + glfwPollEvents() + var display_w, display_h : int + glfwGetFramebufferSize(window, display_w, display_h) + glViewport(0, 0, display_w, display_h) + glUseProgram(display_program) + c_frame++ + vs_effect_bind_uniform(display_program) + fs_effect_bind_uniform(display_program) + glBindVertexArray(vao) + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo) + glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, null) + // swap buffers + glfwMakeContextCurrent(window) + glfwSwapBuffers(window) +} + +[export] +def shutdown { + glfwDestroyWindow(window) + glfwTerminate() +} + [export] def main { var r <- parse_args(type) @@ -100,40 +143,15 @@ def main { print_help(get_command_info(type), "toy_pathtracer_opengl_basic") return } - if (glfwInit() == 0) { - panic("can't init glfw") - } - defer() { - glfwTerminate() - } - glfwInitOpenGL(3, 3) - var window = glfwCreateWindow(TRACE_WIDTH, TRACE_HEIGHT, "OpenGL Path tracer", null, null) - if (window == null) { - panic("can't create window") - } - defer() { - glfwDestroyWindow(window) - } - glfwSetWindowSize(window, TRACE_WIDTH, TRACE_HEIGHT) - glfwMakeContextCurrent(window) - create_gl_objects() + init() + var frame_count = 0 while (glfwWindowShouldClose(window) == 0) { - glfwPollEvents() - var display_w, display_h : int - glfwGetFramebufferSize(window, display_w, display_h) - glViewport(0, 0, display_w, display_h) - glUseProgram(display_program) - c_frame++ - vs_effect_bind_uniform(display_program) - fs_effect_bind_uniform(display_program) - glBindVertexArray(vao) - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo) - glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, null) - // swap buffers - glfwMakeContextCurrent(window) - glfwSwapBuffers(window) - if (cfg.frames > 0 && c_frame >= cfg.frames) { + update() + maybe_collect_gc() + frame_count++ + if (cfg.frames > 0 && frame_count >= cfg.frames) { break // headless smoke / test mode: stop after the requested frame count } } + shutdown() } From 928270f22648d46d983e92b03ae4b7775da33c8b Mon Sep 17 00:00:00 2001 From: Boris Batkin Date: Thu, 30 Jul 2026 04:36:02 -0700 Subject: [PATCH 4/6] examples/opengl: all 10 GLFW viewers to the canonical application shape Each monolithic main splits into the exported trio + canonical main (update + maybe_collect_gc per frame, arcanoid-pattern --max-frames bound); frame bodies verbatim, defers become shutdown() in LIFO order, needed locals hoisted to module globals. 09/10/11's GLFW callbacks drop their by-ref captures of init() locals for globals -- required, not stylistic: those captures would dangle once init returns. Wasm host note added per file (the trio makes these web-drivable; main is desktop-only). All 10 run-proven windowed: 60-frame bounded runs exit 0. Lint + dasfmt clean. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01NcrnLGE9AEh8EAND3xYCRC --- examples/opengl/01_hello_triangle.das | 91 ++++++--- examples/opengl/02_hello_image.das | 99 ++++++---- examples/opengl/03_hello_cube.das | 120 +++++++----- .../opengl/04_hello_render_to_texture.das | 119 +++++++----- examples/opengl/05_hello_compute.das | 141 ++++++++------ examples/opengl/06_hello_ttf.das | 98 ++++++---- examples/opengl/07_hello_gen.das | 151 +++++++++------ examples/opengl/09_hello_mesh.das | 134 ++++++++----- examples/opengl/10_hello_gltf.das | 144 ++++++++------ examples/opengl/11_hello_gltf_animation.das | 179 +++++++++++------- 10 files changed, 791 insertions(+), 485 deletions(-) diff --git a/examples/opengl/01_hello_triangle.das b/examples/opengl/01_hello_triangle.das index 9b8bc58171..334ca29bde 100644 --- a/examples/opengl/01_hello_triangle.das +++ b/examples/opengl/01_hello_triangle.das @@ -1,10 +1,12 @@ options gen2 +options persistent_heap +options gc require glfw/glfw_boost require opengl/opengl_boost -require daslib/defer -require math +require strings require daslib/safe_addr +require live/live_gc var @in @location v_position : float2 var @in @location v_color : float3 @@ -22,6 +24,7 @@ def fs_main { f_FragColor = float4(f_color, 1.0) } +var window : GLFWwindow? var program : uint var vao : uint var vbo : uint @@ -51,44 +54,70 @@ def create_gl_objects { } [export] -def main { +def init() { if (glfwInit() == 0) { panic("can't init glfw") } - defer() { - glfwTerminate() - } glfwInitOpenGL(3, 3) - var window = glfwCreateWindow(1280, 720, "OpenGL - Hello triangle", null, null) + window = glfwCreateWindow(1280, 720, "OpenGL - Hello triangle", null, null) if (window == null) { panic("can't create window") } - defer() { - glfwDestroyWindow(window) - } glfwMakeContextCurrent(window) create_gl_objects() - eval_main_loop() { - if (glfwWindowShouldClose(window) != 0) { - return false +} + +[export] +def update() { + glfwPollEvents() + var display_w, display_h : int + glfwGetFramebufferSize(window, display_w, display_h) + // render + glViewport(0, 0, display_w, display_h) + glClearColor(0.2, 0.2, 0.2, 1.0) + glClearDepth(1.0lf) + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) + glDepthFunc(GL_LEQUAL) + glUseProgram(program) + vs_main_bind_uniform(program) + fs_main_bind_uniform(program) + glBindVertexArray(vao) + glDrawArrays(GL_TRIANGLES, 0, 3) + // swap buffers + glfwMakeContextCurrent(window) + glfwSwapBuffers(window) +} + +[export] +def shutdown() { + glfwDestroyWindow(window) + glfwTerminate() +} + +var max_frame_limit = 0 + +def parse_args() { + let args <- get_command_line_arguments() + for (i in range(length(args) - 1)) { + if (args[i] == "--max-frames") { + max_frame_limit = to_int(args[i + 1]) + } + } +} + +// on web the wasm host auto-drives init/update/shutdown per frame and never calls main +[export] +def main() { + parse_args() + init() + var frame_count = 0 + while (glfwWindowShouldClose(window) == 0) { + update() + maybe_collect_gc() + frame_count ++ + if (max_frame_limit > 0 && frame_count >= max_frame_limit) { + break } - glfwPollEvents() - var display_w, display_h : int - glfwGetFramebufferSize(window, display_w, display_h) - // render - glViewport(0, 0, display_w, display_h) - glClearColor(0.2, 0.2, 0.2, 1.0) - glClearDepth(1.0lf) - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) - glDepthFunc(GL_LEQUAL) - glUseProgram(program) - vs_main_bind_uniform(program) - fs_main_bind_uniform(program) - glBindVertexArray(vao) - glDrawArrays(GL_TRIANGLES, 0, 3) - // swap buffers - glfwMakeContextCurrent(window) - glfwSwapBuffers(window) - return true } + shutdown() } diff --git a/examples/opengl/02_hello_image.das b/examples/opengl/02_hello_image.das index a1975703b1..8241789e5a 100644 --- a/examples/opengl/02_hello_image.das +++ b/examples/opengl/02_hello_image.das @@ -1,11 +1,13 @@ options gen2 +options persistent_heap +options gc require glfw/glfw_boost require opengl/opengl_boost require glsl/glsl_opengl -require daslib/defer -require math +require strings require daslib/safe_addr +require live/live_gc var @in @location v_position : float2 var @in @location v_color : float3 @@ -27,6 +29,7 @@ def fs_main { f_FragColor = texture(f_tex, f_texcoord) * float4(f_color, 1.0) } +var window : GLFWwindow? var program : uint var vao : uint var vbo : uint @@ -71,47 +74,73 @@ def create_gl_objects { } [export] -def main { +def init() { if (glfwInit() == 0) { panic("can't init glfw") } - defer() { - glfwTerminate() - } glfwInitOpenGL(3, 3) - var window = glfwCreateWindow(1280, 720, "OpenGL - Hello image", null, null) + window = glfwCreateWindow(1280, 720, "OpenGL - Hello image", null, null) if (window == null) { panic("can't create window") } - defer() { - glfwDestroyWindow(window) - } glfwMakeContextCurrent(window) create_gl_objects() - eval_main_loop() { - if (glfwWindowShouldClose(window) != 0) { - return false +} + +[export] +def update() { + glfwPollEvents() + var display_w, display_h : int + glfwGetFramebufferSize(window, display_w, display_h) + // render + glViewport(0, 0, display_w, display_h) + glClearColor(0.2, 0.2, 0.2, 1.0) + glClearDepth(1.0lf) + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) + glEnable(GL_DEPTH_TEST) + glDepthFunc(GL_LEQUAL) + glUseProgram(program) + f_tex := texture // bind texture + vs_main_bind_uniform(program) + fs_main_bind_uniform(program) + glBindVertexArray(vao) + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo) + glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, null) + // swap buffersa + glfwMakeContextCurrent(window) + glfwSwapBuffers(window) +} + +[export] +def shutdown() { + glfwDestroyWindow(window) + glfwTerminate() +} + +var max_frame_limit = 0 + +def parse_args() { + let args <- get_command_line_arguments() + for (i in range(length(args) - 1)) { + if (args[i] == "--max-frames") { + max_frame_limit = to_int(args[i + 1]) + } + } +} + +// on web the wasm host auto-drives init/update/shutdown per frame and never calls main +[export] +def main() { + parse_args() + init() + var frame_count = 0 + while (glfwWindowShouldClose(window) == 0) { + update() + maybe_collect_gc() + frame_count ++ + if (max_frame_limit > 0 && frame_count >= max_frame_limit) { + break } - glfwPollEvents() - var display_w, display_h : int - glfwGetFramebufferSize(window, display_w, display_h) - // render - glViewport(0, 0, display_w, display_h) - glClearColor(0.2, 0.2, 0.2, 1.0) - glClearDepth(1.0lf) - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) - glEnable(GL_DEPTH_TEST) - glDepthFunc(GL_LEQUAL) - glUseProgram(program) - f_tex := texture // bind texture - vs_main_bind_uniform(program) - fs_main_bind_uniform(program) - glBindVertexArray(vao) - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo) - glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, null) - // swap buffersa - glfwMakeContextCurrent(window) - glfwSwapBuffers(window) - return true } -} \ No newline at end of file + shutdown() +} diff --git a/examples/opengl/03_hello_cube.das b/examples/opengl/03_hello_cube.das index edd82927ed..16abc8451a 100644 --- a/examples/opengl/03_hello_cube.das +++ b/examples/opengl/03_hello_cube.das @@ -1,11 +1,13 @@ options gen2 +options persistent_heap +options gc require glfw/glfw_boost require opengl/opengl_boost -require daslib/defer -require daslib/math_boost +require strings require daslib/safe_addr require daslib/math_boost +require live/live_gc var @in @location v_position : float3 var @in @location v_normal : float3 @@ -33,6 +35,7 @@ def fs_main { f_FragColor.w = 1. } +var window : GLFWwindow? var program : uint var vao : uint var vbo : uint @@ -102,61 +105,86 @@ def create_gl_objects { } [export] -def main { +def init() { if (glfwInit() == 0) { panic("can't init glfw") } - defer() { - glfwTerminate() - } glfwInitOpenGL(3, 3) - var window = glfwCreateWindow(1280, 720, "OpenGL - Hello cube", null, null) + window = glfwCreateWindow(1280, 720, "OpenGL - Hello cube", null, null) if (window == null) { panic("can't create window") } - defer() { - glfwDestroyWindow(window) - } glfwMakeContextCurrent(window) create_gl_objects() v_view = look_at_rh(float3(0, 0, 10), float3(0, 0, 0), float3(0, 1, 0)) - eval_main_loop() { - if (glfwWindowShouldClose(window) != 0) { - return false - } - glfwPollEvents() - var display_w, display_h : int - glfwGetFramebufferSize(window, display_w, display_h) - let aspect = display_h != 0 ? float(display_w) / float(display_h) : 1. - // render - glViewport(0, 0, display_w, display_h) - glClearColor(0.2, 0.2, 0.2, 1.0) - glClearDepth(1.0lf) - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) - glEnable(GL_DEPTH_TEST) - glEnable(GL_CULL_FACE) - glCullFace(GL_BACK) - glDepthFunc(GL_LEQUAL) - let t = glfwGetTime() - if (t > 10lf) { - glfwSetTime(t - 10lf) +} + +[export] +def update() { + glfwPollEvents() + var display_w, display_h : int + glfwGetFramebufferSize(window, display_w, display_h) + let aspect = display_h != 0 ? float(display_w) / float(display_h) : 1. + // render + glViewport(0, 0, display_w, display_h) + glClearColor(0.2, 0.2, 0.2, 1.0) + glClearDepth(1.0lf) + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) + glEnable(GL_DEPTH_TEST) + glEnable(GL_CULL_FACE) + glCullFace(GL_BACK) + glDepthFunc(GL_LEQUAL) + let t = glfwGetTime() + if (t > 10lf) { + glfwSetTime(t - 10lf) + } + let rot = quat_from_unit_vec_ang(normalize(float3(1., 1., 0.)), float(t) * PI * 2. * 0.1) + v_model = compose(float3(0, 0, 0), rot, float3(1.0)) // bind model + v_projection = perspective_rh_opengl(45.0f * PI / 180., aspect, 0.1f, 50.0f)// bind projection + glUseProgram(program) + f_tex := texture + vs_main_bind_uniform(program) + fs_main_bind_uniform(program) + glBindVertexArray(vao) + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo) + glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, null) + glUseProgram(0u) + bind_ffp() + // swap buffers + glfwMakeContextCurrent(window) + glfwSwapBuffers(window) +} + +[export] +def shutdown() { + glfwDestroyWindow(window) + glfwTerminate() +} + +var max_frame_limit = 0 + +def parse_args() { + let args <- get_command_line_arguments() + for (i in range(length(args) - 1)) { + if (args[i] == "--max-frames") { + max_frame_limit = to_int(args[i + 1]) } - let rot = quat_from_unit_vec_ang(normalize(float3(1., 1., 0.)), float(t) * PI * 2. * 0.1) - v_model = compose(float3(0, 0, 0), rot, float3(1.0)) // bind model - v_projection = perspective_rh_opengl(45.0f * PI / 180., aspect, 0.1f, 50.0f)// bind projection - glUseProgram(program) - f_tex := texture - vs_main_bind_uniform(program) - fs_main_bind_uniform(program) - glBindVertexArray(vao) - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo) - glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, null) - glUseProgram(0u) - bind_ffp() - // swap buffers - glfwMakeContextCurrent(window) - glfwSwapBuffers(window) - return true } } +// on web the wasm host auto-drives init/update/shutdown per frame and never calls main +[export] +def main() { + parse_args() + init() + var frame_count = 0 + while (glfwWindowShouldClose(window) == 0) { + update() + maybe_collect_gc() + frame_count ++ + if (max_frame_limit > 0 && frame_count >= max_frame_limit) { + break + } + } + shutdown() +} diff --git a/examples/opengl/04_hello_render_to_texture.das b/examples/opengl/04_hello_render_to_texture.das index 026fe309bd..c759fe0a32 100644 --- a/examples/opengl/04_hello_render_to_texture.das +++ b/examples/opengl/04_hello_render_to_texture.das @@ -1,11 +1,14 @@ options gen2 +options persistent_heap +options gc require glfw/glfw_boost require opengl/opengl_boost require glsl/glsl_opengl -require daslib/defer require math +require strings require daslib/safe_addr +require live/live_gc var @in @location v_position : float2 var @in @location v_color : float3 @@ -47,6 +50,7 @@ def fs_display { f_FragColor.w = 1. } +var window : GLFWwindow? var program, display_program : uint var vao : uint var vbo : uint @@ -109,60 +113,85 @@ def create_gl_objects { } [export] -def main { +def init() { if (glfwInit() == 0) { panic("can't init glfw") } - defer() { - glfwTerminate() - } glfwInitOpenGL(3, 3) - var window = glfwCreateWindow(1280, 720, "OpenGL - Hello render to texture", null, null) + window = glfwCreateWindow(1280, 720, "OpenGL - Hello render to texture", null, null) if (window == null) { panic("can't create window") } - defer() { - glfwDestroyWindow(window) - } glfwMakeContextCurrent(window) create_gl_objects() - eval_main_loop() { - if (glfwWindowShouldClose(window) != 0) { - return false - } - glfwPollEvents() - var display_w, display_h : int - glfwGetFramebufferSize(window, display_w, display_h) - let t = glfwGetTime() - if (t > 1lf) { - glfwSetTime(t - 1lf) +} + +[export] +def update() { + glfwPollEvents() + var display_w, display_h : int + glfwGetFramebufferSize(window, display_w, display_h) + let t = glfwGetTime() + if (t > 1lf) { + glfwSetTime(t - 1lf) + } + let ft = float(t) + f_offset = float2(ft, sin(ft * PI * 2.) * 0.25) + // bind fbo and render to it + glBindFramebuffer(GL_FRAMEBUFFER, fbo) + glViewport(0, 0, fbo_width, fbo_height) + glUseProgram(program) + f_tex := texture + vs_effect_bind_uniform(program) + fs_effect_bind_uniform(program) + glBindVertexArray(vao) + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo) + glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, null) + // bind back buffer, and render to it + glBindFramebuffer(GL_FRAMEBUFFER, 0u) + glViewport(0, 0, display_w, display_h) + glUseProgram(display_program) + f_tex := target_texture + vs_effect_bind_uniform(display_program) + fs_display_bind_uniform(display_program) + glBindVertexArray(vao) + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo) + glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, null) + // swap buffers + glfwMakeContextCurrent(window) + glfwSwapBuffers(window) +} + +[export] +def shutdown() { + glfwDestroyWindow(window) + glfwTerminate() +} + +var max_frame_limit = 0 + +def parse_args() { + let args <- get_command_line_arguments() + for (i in range(length(args) - 1)) { + if (args[i] == "--max-frames") { + max_frame_limit = to_int(args[i + 1]) } - let ft = float(t) - f_offset = float2(ft, sin(ft * PI * 2.) * 0.25) - // bind fbo and render to it - glBindFramebuffer(GL_FRAMEBUFFER, fbo) - glViewport(0, 0, fbo_width, fbo_height) - glUseProgram(program) - f_tex := texture - vs_effect_bind_uniform(program) - fs_effect_bind_uniform(program) - glBindVertexArray(vao) - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo) - glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, null) - // bind back buffer, and render to it - glBindFramebuffer(GL_FRAMEBUFFER, 0u) - glViewport(0, 0, display_w, display_h) - glUseProgram(display_program) - f_tex := target_texture - vs_effect_bind_uniform(display_program) - fs_display_bind_uniform(display_program) - glBindVertexArray(vao) - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo) - glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, null) - // swap buffers - glfwMakeContextCurrent(window) - glfwSwapBuffers(window) - return true } } +// on web the wasm host auto-drives init/update/shutdown per frame and never calls main +[export] +def main() { + parse_args() + init() + var frame_count = 0 + while (glfwWindowShouldClose(window) == 0) { + update() + maybe_collect_gc() + frame_count ++ + if (max_frame_limit > 0 && frame_count >= max_frame_limit) { + break + } + } + shutdown() +} diff --git a/examples/opengl/05_hello_compute.das b/examples/opengl/05_hello_compute.das index 9c8ce308c4..80428fab8b 100644 --- a/examples/opengl/05_hello_compute.das +++ b/examples/opengl/05_hello_compute.das @@ -1,11 +1,14 @@ options gen2 +options persistent_heap +options gc require glfw/glfw_boost require opengl/opengl_boost require glsl/glsl_opengl -require daslib/defer require math +require strings require daslib/safe_addr +require live/live_gc struct MatTest { a, b, a_times_b, b_times_a : float4x4 @@ -53,6 +56,7 @@ def compute_effect { } } +var window : GLFWwindow? var compute_program, display_program : uint var vao : uint var vbo : uint @@ -103,71 +107,96 @@ def create_gl_objects { } [export] -def main { +def init() { if (glfwInit() == 0) { panic("can't init glfw") } - defer() { - glfwTerminate() - } glfwInitOpenGL(4, 6) - var window = glfwCreateWindow(1280, 720, "OpenGL - Hello compute shader", null, null) + window = glfwCreateWindow(1280, 720, "OpenGL - Hello compute shader", null, null) if (window == null) { panic("can't create window") } - defer() { - glfwDestroyWindow(window) - } glfwMakeContextCurrent(window) create_gl_objects() - eval_main_loop() { - if (glfwWindowShouldClose(window) != 0) { - return false +} + +[export] +def update() { + glfwPollEvents() + var display_w, display_h : int + glfwGetFramebufferSize(window, display_w, display_h) + // fill c_mat with values + let rot = quat_from_unit_vec_ang(normalize(float3(1., 0., 0.)), PI * 0.25) + c_mat.a = compose(float3(0.), rot, float3(1.)) + c_mat.b = translation(float3(1., 2., 3.)) + identity(c_mat.a_times_b) + identity(c_mat.b_times_a) + c_mat.a_times_1234 = float4(0.) + c_mat.b_times_1234 = float4(0.) + // run program + glUseProgram(compute_program) + write_ssbo(ssbo, c_mat) // upload c_mat + c_destTex := texture + compute_effect_bind_uniform(compute_program) + glDispatchCompute(16u, 16u, 1u) + glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT | GL_SHADER_STORAGE_BARRIER_BIT) + read_ssbo(ssbo, c_mat) // read c_mat back + c_offset ++ + // make a copy in e_mat, do computations on CPU + var e_mat = c_mat + e_mat.a_times_b = e_mat.a * e_mat.b + e_mat.b_times_a = e_mat.b * e_mat.a + e_mat.a_times_1234 = e_mat.a * float4(1, 2, 3, 4) + e_mat.b_times_1234 = e_mat.b * float4(1, 2, 3, 4) + // compare CPU computations with GPU computaions + assert(e_mat.a_times_b == c_mat.a_times_b) + assert(e_mat.b_times_a == c_mat.b_times_a) + assert(e_mat.a_times_1234 == c_mat.a_times_1234) + assert(e_mat.b_times_1234 == c_mat.b_times_1234) + // render + glViewport(0, 0, display_w, display_h) + glUseProgram(display_program) + glBindBuffer(GL_ARRAY_BUFFER, vbo) + f_tex := texture + vs_effect_bind_uniform(display_program) + fs_effect_bind_uniform(display_program) + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo) + glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, null) + // swap buffers + glfwMakeContextCurrent(window) + glfwSwapBuffers(window) +} + +[export] +def shutdown() { + glfwDestroyWindow(window) + glfwTerminate() +} + +var max_frame_limit = 0 + +def parse_args() { + let args <- get_command_line_arguments() + for (i in range(length(args) - 1)) { + if (args[i] == "--max-frames") { + max_frame_limit = to_int(args[i + 1]) } - glfwPollEvents() - var display_w, display_h : int - glfwGetFramebufferSize(window, display_w, display_h) - // fill c_mat with values - let rot = quat_from_unit_vec_ang(normalize(float3(1., 0., 0.)), PI * 0.25) - c_mat.a = compose(float3(0.), rot, float3(1.)) - c_mat.b = translation(float3(1., 2., 3.)) - identity(c_mat.a_times_b) - identity(c_mat.b_times_a) - c_mat.a_times_1234 = float4(0.) - c_mat.b_times_1234 = float4(0.) - // run program - glUseProgram(compute_program) - write_ssbo(ssbo, c_mat) // upload c_mat - c_destTex := texture - compute_effect_bind_uniform(compute_program) - glDispatchCompute(16u, 16u, 1u) - glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT | GL_SHADER_STORAGE_BARRIER_BIT) - read_ssbo(ssbo, c_mat) // read c_mat back - c_offset ++ - // make a copy in e_mat, do computations on CPU - var e_mat = c_mat - e_mat.a_times_b = e_mat.a * e_mat.b - e_mat.b_times_a = e_mat.b * e_mat.a - e_mat.a_times_1234 = e_mat.a * float4(1, 2, 3, 4) - e_mat.b_times_1234 = e_mat.b * float4(1, 2, 3, 4) - // compare CPU computations with GPU computaions - assert(e_mat.a_times_b == c_mat.a_times_b) - assert(e_mat.b_times_a == c_mat.b_times_a) - assert(e_mat.a_times_1234 == c_mat.a_times_1234) - assert(e_mat.b_times_1234 == c_mat.b_times_1234) - // render - glViewport(0, 0, display_w, display_h) - glUseProgram(display_program) - glBindBuffer(GL_ARRAY_BUFFER, vbo) - f_tex := texture - vs_effect_bind_uniform(display_program) - fs_effect_bind_uniform(display_program) - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo) - glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, null) - // swap buffers - glfwMakeContextCurrent(window) - glfwSwapBuffers(window) - return true } } +// on web the wasm host auto-drives init/update/shutdown per frame and never calls main +[export] +def main() { + parse_args() + init() + var frame_count = 0 + while (glfwWindowShouldClose(window) == 0) { + update() + maybe_collect_gc() + frame_count ++ + if (max_frame_limit > 0 && frame_count >= max_frame_limit) { + break + } + } + shutdown() +} diff --git a/examples/opengl/06_hello_ttf.das b/examples/opengl/06_hello_ttf.das index 6c6c9d5a13..c5247ba5e4 100644 --- a/examples/opengl/06_hello_ttf.das +++ b/examples/opengl/06_hello_ttf.das @@ -1,29 +1,29 @@ options gen2 +options persistent_heap +options gc require glfw/glfw_boost require opengl/opengl_boost require opengl/opengl_ttf -require daslib/defer require math +require strings require daslib/math_boost +require live/live_gc +var window : GLFWwindow? var font : Font +var hw_text : array +var model : float4x4 [export] -def main { +def init() { if (glfwInit() == 0) { panic("can't init glfw") } - defer() { - glfwTerminate() - } glfwInitOpenGL(3, 3) - var window = glfwCreateWindow(1280, 720, "OpenGL - Hello opengl_ttf", null, null) + window = glfwCreateWindow(1280, 720, "OpenGL - Hello opengl_ttf", null, null) if (window == null) { panic("can't create window") } - defer() { - glfwDestroyWindow(window) - } glfwMakeContextCurrent(window) // create ttf internals, load font with oversampling=2 for GPU rendering create_ttf_objects() @@ -36,32 +36,64 @@ def main { } upload_font_texture(font) // create vertex arrays with quads, which render particular text - let hw_text <- font |> create_quads("Hello, world") + hw_text <- font |> create_quads("Hello, world") // make a translate-scale matrix, so that text is in the middle, and 1.0 wide let dim = quads_dim(hw_text) - let model = quads_view(hw_text, float2(3. / (dim.vmax.x - dim.vmin.x))) - eval_main_loop() { - if (glfwWindowShouldClose(window) != 0) { - return false + model = quads_view(hw_text, float2(3. / (dim.vmax.x - dim.vmin.x))) +} + +[export] +def update() { + glfwPollEvents() + var display_w, display_h : int + glfwGetFramebufferSize(window, display_w, display_h) + let aspect = display_h != 0 ? float(display_w) / float(display_h) : 1. + // render + glViewport(0, 0, display_w, display_h) + glClearColor(0.2, 0.2, 0.2, 1.0) + glClearDepth(1.0lf) + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) + // model-view-projection setup + let view = translation(float3(0.0f, 0.0f, -4.0f)) + let projection = perspective_rh_minus1_to_1(45.0f * PI / 180., aspect, 0.1f, 50.0f) + let mvp = projection * view * model + font |> draw_quads(hw_text, mvp) + font |> draw_quads_2d(hw_text, display_w, display_h, 100, 100) + // swap buffers + glfwMakeContextCurrent(window) + glfwSwapBuffers(window) +} + +[export] +def shutdown() { + glfwDestroyWindow(window) + glfwTerminate() +} + +var max_frame_limit = 0 + +def parse_args() { + let args <- get_command_line_arguments() + for (i in range(length(args) - 1)) { + if (args[i] == "--max-frames") { + max_frame_limit = to_int(args[i + 1]) + } + } +} + +// on web the wasm host auto-drives init/update/shutdown per frame and never calls main +[export] +def main() { + parse_args() + init() + var frame_count = 0 + while (glfwWindowShouldClose(window) == 0) { + update() + maybe_collect_gc() + frame_count ++ + if (max_frame_limit > 0 && frame_count >= max_frame_limit) { + break } - glfwPollEvents() - var display_w, display_h : int - glfwGetFramebufferSize(window, display_w, display_h) - let aspect = display_h != 0 ? float(display_w) / float(display_h) : 1. - // render - glViewport(0, 0, display_w, display_h) - glClearColor(0.2, 0.2, 0.2, 1.0) - glClearDepth(1.0lf) - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) - // model-view-projection setup - let view = translation(float3(0.0f, 0.0f, -4.0f)) - let projection = perspective_rh_minus1_to_1(45.0f * PI / 180., aspect, 0.1f, 50.0f) - let mvp = projection * view * model - font |> draw_quads(hw_text, mvp) - font |> draw_quads_2d(hw_text, display_w, display_h, 100, 100) - // swap buffers - glfwMakeContextCurrent(window) - glfwSwapBuffers(window) - return true } + shutdown() } diff --git a/examples/opengl/07_hello_gen.das b/examples/opengl/07_hello_gen.das index f21860ff91..801b69bd52 100644 --- a/examples/opengl/07_hello_gen.das +++ b/examples/opengl/07_hello_gen.das @@ -1,10 +1,13 @@ options gen2 +options persistent_heap +options gc require glfw/glfw_boost require opengl/opengl_boost require opengl/opengl_gen -require daslib/defer require math +require strings require daslib/math_boost +require live/live_gc var @in @location v_position : float3 var @in @location v_normal : float3 @@ -43,78 +46,108 @@ def fs_preview { f_FragColor.w = 1. } +var window : GLFWwindow? +var program : uint +var sphere, cube, cylinder : OpenGLGeometryFragment +var checkerboard_texture : uint + [export] -def main { +def init() { if (glfwInit() == 0) { panic("can't init glfw") } - defer() { - glfwTerminate() - } glfwInitOpenGL(3, 3) - var window = glfwCreateWindow(1280, 720, "OpenGL - Hello generated geometry", null, null) + window = glfwCreateWindow(1280, 720, "OpenGL - Hello generated geometry", null, null) if (window == null) { panic("can't create window") } - defer() { - glfwDestroyWindow(window) - } glfwMakeContextCurrent(window) - let program = create_shader_program(@@vs_preview, @@fs_preview) - let sphere <- gen_sphere(32, 16, false) |> create_geometry_fragment - let cube <- gen_cube() |> create_geometry_fragment - let cylinder <- gen_cylinder(GenDirection.xz, 32) |> create_geometry_fragment - let checkerboard_texture = gen_image_checkerboard(16, 16, 0xff404040, 0xff808080) |> create_texture + program = create_shader_program(@@vs_preview, @@fs_preview) + sphere <- gen_sphere(32, 16, false) |> create_geometry_fragment + cube <- gen_cube() |> create_geometry_fragment + cylinder <- gen_cylinder(GenDirection.xz, 32) |> create_geometry_fragment + checkerboard_texture = gen_image_checkerboard(16, 16, 0xff404040, 0xff808080) |> create_texture glBindTexture(GL_TEXTURE_2D, checkerboard_texture) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST) glBindTexture(GL_TEXTURE_2D, 0u) - eval_main_loop() { - if (glfwWindowShouldClose(window) != 0) { - return false - } - glfwPollEvents() - var display_w, display_h : int - glfwGetFramebufferSize(window, display_w, display_h) - let aspect = display_h != 0 ? float(display_w) / float(display_h) : 1. - // render - glViewport(0, 0, display_w, display_h) - glClearColor(0.2, 0.2, 0.2, 1.0) - glClearDepth(1.0lf) - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) - glEnable(GL_DEPTH_TEST) - glEnable(GL_CULL_FACE) - glCullFace(GL_BACK) - glDepthFunc(GL_LEQUAL) - let t = glfwGetTime() - if (t > 10lf) { - glfwSetTime(t - 10lf) +} + +[export] +def update() { + glfwPollEvents() + var display_w, display_h : int + glfwGetFramebufferSize(window, display_w, display_h) + let aspect = display_h != 0 ? float(display_w) / float(display_h) : 1. + // render + glViewport(0, 0, display_w, display_h) + glClearColor(0.2, 0.2, 0.2, 1.0) + glClearDepth(1.0lf) + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) + glEnable(GL_DEPTH_TEST) + glEnable(GL_CULL_FACE) + glCullFace(GL_BACK) + glDepthFunc(GL_LEQUAL) + let t = glfwGetTime() + if (t > 10lf) { + glfwSetTime(t - 10lf) + } + f_camera_position = float3(0, 0, 8) + v_view = look_at_rh(f_camera_position, float3(0, 0, 0), float3(0, 1, 0)) + let rot = quat_from_unit_vec_ang(normalize(float3(1., 1., 0.)), float(t) * PI * 2. * 0.1) + v_projection = perspective_rh_opengl(45.0f * PI / 180., aspect, 0.1f, 50.0f) + f_tex := checkerboard_texture + glUseProgram(program) + // cube + v_model = compose(float3(-3, 0, 0), rot, float3(1.0)) + vs_preview_bind_uniform(program) + fs_preview_bind_uniform(program) + draw_geometry_fragment(cube) + // sphere + v_model = compose(float3(0, 0, 0), rot, float3(1.0)) + vs_preview_bind_uniform(program) + fs_preview_bind_uniform(program) + draw_geometry_fragment(sphere) + // cylinder + v_model = compose(float3(3, 0, 0), rot, float3(1.0)) + vs_preview_bind_uniform(program) + fs_preview_bind_uniform(program) + draw_geometry_fragment(cylinder) + glUseProgram(0u) + glfwMakeContextCurrent(window) + glfwSwapBuffers(window) +} + +[export] +def shutdown() { + glfwDestroyWindow(window) + glfwTerminate() +} + +var max_frame_limit = 0 + +def parse_args() { + let args <- get_command_line_arguments() + for (i in range(length(args) - 1)) { + if (args[i] == "--max-frames") { + max_frame_limit = to_int(args[i + 1]) } - f_camera_position = float3(0, 0, 8) - v_view = look_at_rh(f_camera_position, float3(0, 0, 0), float3(0, 1, 0)) - let rot = quat_from_unit_vec_ang(normalize(float3(1., 1., 0.)), float(t) * PI * 2. * 0.1) - v_projection = perspective_rh_opengl(45.0f * PI / 180., aspect, 0.1f, 50.0f) - f_tex := checkerboard_texture - glUseProgram(program) - // cube - v_model = compose(float3(-3, 0, 0), rot, float3(1.0)) - vs_preview_bind_uniform(program) - fs_preview_bind_uniform(program) - draw_geometry_fragment(cube) - // sphere - v_model = compose(float3(0, 0, 0), rot, float3(1.0)) - vs_preview_bind_uniform(program) - fs_preview_bind_uniform(program) - draw_geometry_fragment(sphere) - // cylinder - v_model = compose(float3(3, 0, 0), rot, float3(1.0)) - vs_preview_bind_uniform(program) - fs_preview_bind_uniform(program) - draw_geometry_fragment(cylinder) - glUseProgram(0u) - glfwMakeContextCurrent(window) - glfwSwapBuffers(window) - return true } } +// on web the wasm host auto-drives init/update/shutdown per frame and never calls main +[export] +def main() { + parse_args() + init() + var frame_count = 0 + while (glfwWindowShouldClose(window) == 0) { + update() + maybe_collect_gc() + frame_count ++ + if (max_frame_limit > 0 && frame_count >= max_frame_limit) { + break + } + } + shutdown() +} diff --git a/examples/opengl/09_hello_mesh.das b/examples/opengl/09_hello_mesh.das index 246b90424a..432d725595 100644 --- a/examples/opengl/09_hello_mesh.das +++ b/examples/opengl/09_hello_mesh.das @@ -1,10 +1,13 @@ options gen2 +options persistent_heap +options gc require glfw/glfw_boost require opengl/opengl_boost require opengl/opengl_gen -require daslib/defer require math +require strings require daslib/math_boost +require live/live_gc var @in @location v_position : float3 var @in @location v_normal : float3 @@ -43,48 +46,48 @@ def fs_preview { f_FragColor.w = 1. } +var window : GLFWwindow? +var program : uint +var mesh : OpenGLGeometryFragment +var checkerboard_texture : uint +var camera_distance = 4. +var camera_rotation : float +var camera_position_y : float +var cursor_position_xy : float2 +var cursor_enabled = false + [export] -def main { +def init() { if (glfwInit() == 0) { panic("can't init glfw") } - defer() { - glfwTerminate() - } glfwInitOpenGL(3, 3) - var window = glfwCreateWindow(1280, 720, "OpenGL - Hello object file", null, null) + window = glfwCreateWindow(1280, 720, "OpenGL - Hello object file", null, null) if (window == null) { panic("can't create window") } - defer() { - glfwDestroyWindow(window) - } glfwMakeContextCurrent(window) - let program = create_shader_program(@@vs_preview, @@fs_preview) + program = create_shader_program(@@vs_preview, @@fs_preview) // var mesh <- gen_sphere(32,16,false) |> create_geometry_fragment - let mesh <- load_obj_mesh("orb.obj", + mesh <- load_obj_mesh("orb.obj", "{get_das_root()}/examples/opengl", "{get_das_root()}/examples/opengl/media" ) |> create_geometry_fragment - let checkerboard_texture = gen_image_checkerboard(16, 16, 0xff404040, 0xff808080) |> create_texture + checkerboard_texture = gen_image_checkerboard(16, 16, 0xff404040, 0xff808080) |> create_texture glBindTexture(GL_TEXTURE_2D, checkerboard_texture) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST) glBindTexture(GL_TEXTURE_2D, 0u) - var camera_distance = 4. - var camera_rotation : float - var camera_position_y : float - var cursor_position_xy = glfwGetCursorPos(window) - var cursor_enabled = false + cursor_position_xy = glfwGetCursorPos(window) unsafe { - glfwSetScrollCallback(window) @ capture(& camera_distance) (w, x, y) { + glfwSetScrollCallback(window) @(w, x, y) { if (y > 0.0lf) { camera_distance *= 1.1 } else { camera_distance /= 1.1 } } - glfwSetCursorPosCallback(window) @ capture(& cursor_position_xy, & cursor_enabled, & camera_position_y, & camera_rotation, & camera_distance) (w, x, y) { + glfwSetCursorPosCallback(window) @(w, x, y) { if (cursor_enabled) { let cxy = glfwGetCursorPos(window) let dxy = cxy - cursor_position_xy @@ -96,7 +99,7 @@ def main { glfwSetCursorPos(window, double(cursor_position_xy.x), double(cursor_position_xy.y)) } } - glfwSetMouseButtonCallback(window) @ capture(& cursor_enabled, & cursor_position_xy) (w, b, a, m) { + glfwSetMouseButtonCallback(window) @(w, b, a, m) { if (b == int(GLFW_MOUSE_BUTTON_1)) { cursor_enabled = a == int(GLFW_PRESS) if (cursor_enabled) { @@ -113,37 +116,68 @@ def main { } } } - eval_main_loop() { - if (glfwWindowShouldClose(window) != 0) { - return false +} + +[export] +def update() { + glfwPollEvents() + var display_w, display_h : int + glfwGetFramebufferSize(window, display_w, display_h) + let aspect = display_h != 0 ? float(display_w) / float(display_h) : 1. + // render + glViewport(0, 0, display_w, display_h) + glClearColor(0.2, 0.2, 0.2, 1.0) + glClearDepth(1.0lf) + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) + glEnable(GL_DEPTH_TEST) + glEnable(GL_CULL_FACE) + glCullFace(GL_BACK) + glDepthFunc(GL_LEQUAL) + f_camera_position = float3(0., camera_position_y + camera_distance * 0.5, camera_distance) + v_view = look_at_rh(f_camera_position, float3(0, 0, 0), float3(0, 1, 0)) + let rot = quat_from_unit_vec_ang(normalize(float3(0., 1., 0.)), camera_rotation * PI / 180.) + v_projection = perspective_rh_opengl(45.0f * PI / 180., aspect, 0.1f, 50.0f) + f_tex := checkerboard_texture + glUseProgram(program) + v_model = compose(float3(0, 0, 0), rot, float3(1.0)) + vs_preview_bind_uniform(program) + fs_preview_bind_uniform(program) + draw_geometry_fragment(mesh) + glUseProgram(0u) + glfwMakeContextCurrent(window) + glfwSwapBuffers(window) +} + +[export] +def shutdown() { + glfwDestroyWindow(window) + glfwTerminate() +} + +var max_frame_limit = 0 + +def parse_args() { + let args <- get_command_line_arguments() + for (i in range(length(args) - 1)) { + if (args[i] == "--max-frames") { + max_frame_limit = to_int(args[i + 1]) } - glfwPollEvents() - var display_w, display_h : int - glfwGetFramebufferSize(window, display_w, display_h) - let aspect = display_h != 0 ? float(display_w) / float(display_h) : 1. - // render - glViewport(0, 0, display_w, display_h) - glClearColor(0.2, 0.2, 0.2, 1.0) - glClearDepth(1.0lf) - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) - glEnable(GL_DEPTH_TEST) - glEnable(GL_CULL_FACE) - glCullFace(GL_BACK) - glDepthFunc(GL_LEQUAL) - f_camera_position = float3(0., camera_position_y + camera_distance * 0.5, camera_distance) - v_view = look_at_rh(f_camera_position, float3(0, 0, 0), float3(0, 1, 0)) - let rot = quat_from_unit_vec_ang(normalize(float3(0., 1., 0.)), camera_rotation * PI / 180.) - v_projection = perspective_rh_opengl(45.0f * PI / 180., aspect, 0.1f, 50.0f) - f_tex := checkerboard_texture - glUseProgram(program) - v_model = compose(float3(0, 0, 0), rot, float3(1.0)) - vs_preview_bind_uniform(program) - fs_preview_bind_uniform(program) - draw_geometry_fragment(mesh) - glUseProgram(0u) - glfwMakeContextCurrent(window) - glfwSwapBuffers(window) - return true } } +// on web the wasm host auto-drives init/update/shutdown per frame and never calls main +[export] +def main() { + parse_args() + init() + var frame_count = 0 + while (glfwWindowShouldClose(window) == 0) { + update() + maybe_collect_gc() + frame_count ++ + if (max_frame_limit > 0 && frame_count >= max_frame_limit) { + break + } + } + shutdown() +} diff --git a/examples/opengl/10_hello_gltf.das b/examples/opengl/10_hello_gltf.das index ef519e014c..8abdf42450 100644 --- a/examples/opengl/10_hello_gltf.das +++ b/examples/opengl/10_hello_gltf.das @@ -1,13 +1,15 @@ options gen2 +options persistent_heap +options gc require glfw/glfw_boost require opengl/opengl_boost require gltf/gltf_boost require gltf/gltf_gl require gltf/gltf_pbr -require daslib/defer require math require daslib/math_boost require strings +require live/live_gc // dasGLTF — minimal static glTF viewer (OpenGL). Loads a .glb, uploads it to GL, and renders it with // the metallic-roughness PBR shader under an orbit camera (drag to rotate, scroll to zoom). Sibling to @@ -24,64 +26,58 @@ def model_path : string { return "{get_das_root()}/examples/gltf/media/BoomBox.glb" } +var window : GLFWwindow? +var scene : GltfScene +var model : GltfGlModel +var renderer : GltfPbrRenderer +var center : float3 +var radius = 0.001 +var camera_distance : float +var camera_yaw = 0.6 +var camera_pitch = 0.3 +var cursor_prev : float2 +var dragging = false +let light = GltfPbrLight() + [export] -def main { +def init() { if (glfwInit() == 0) { panic("can't init glfw") } - defer() { - glfwTerminate() - } glfwInitOpenGL(3, 3) - var window = glfwCreateWindow(1280, 720, "dasGLTF - Hello glTF (PBR)", null, null) + window = glfwCreateWindow(1280, 720, "dasGLTF - Hello glTF (PBR)", null, null) if (window == null) { panic("can't create window") } - defer() { - glfwDestroyWindow(window) - } glfwMakeContextCurrent(window) let path = model_path() - var scene <- load_gltf(path) - defer() { - delete scene - } + scene <- load_gltf(path) if (empty(scene.meshes)) { panic("no meshes loaded from {path}") } - var model <- upload_gltf(scene) - defer() { - delete model - } - var renderer <- gltf_pbr_create() - defer() { - delete renderer - } + model <- upload_gltf(scene) + renderer <- gltf_pbr_create() // auto-frame: orbit the world-space bounding sphere so any model (BoomBox is ~0.02m) fills the view let bounds = gltf_scene_bounds(scene) - let center = (bounds._0 + bounds._1) * 0.5 - let radius = max(length(bounds._1 - bounds._0) * 0.5, 0.001) + center = (bounds._0 + bounds._1) * 0.5 + radius = max(length(bounds._1 - bounds._0) * 0.5, 0.001) - var camera_distance = radius * 2.2 - var camera_yaw = 0.6 - var camera_pitch = 0.3 - var cursor_prev = glfwGetCursorPos(window) - var dragging = false - let light = GltfPbrLight() + camera_distance = radius * 2.2 + cursor_prev = glfwGetCursorPos(window) unsafe { - glfwSetScrollCallback(window) @ capture(& camera_distance, & radius) (w, x, y) { + glfwSetScrollCallback(window) @(w, x, y) { camera_distance = clamp(camera_distance * (y > 0.0lf ? 0.9 : 1.1), radius * 1.1, radius * 40.0) } - glfwSetMouseButtonCallback(window) @ capture(& dragging, & cursor_prev) (w, b, a, m) { + glfwSetMouseButtonCallback(window) @(w, b, a, m) { if (b == int(GLFW_MOUSE_BUTTON_1)) { dragging = a == int(GLFW_PRESS) cursor_prev = glfwGetCursorPos(window) } } - glfwSetCursorPosCallback(window) @ capture(& dragging, & cursor_prev, & camera_yaw, & camera_pitch) (w, x, y) { + glfwSetCursorPosCallback(window) @(w, x, y) { let cur = glfwGetCursorPos(window) if (dragging) { let d = cur - cursor_prev @@ -91,32 +87,66 @@ def main { cursor_prev = cur } } +} + +[export] +def update() { + glfwPollEvents() + var display_w, display_h : int + glfwGetFramebufferSize(window, display_w, display_h) + let aspect = display_h != 0 ? float(display_w) / float(display_h) : 1. + + let cp = cos(camera_pitch) + let eye = center + float3(cp * sin(camera_yaw), sin(camera_pitch), cp * cos(camera_yaw)) * camera_distance + let view = look_at_rh(eye, center, float3(0., 1., 0.)) + let proj = perspective_rh_minus1_to_1(45.0f * PI / 180., aspect, radius * 0.05, radius * 60.0) + + glViewport(0, 0, display_w, display_h) + glClearColor(0.10, 0.10, 0.12, 1.0) + glClearDepth(1.0lf) + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) + glEnable(GL_DEPTH_TEST) + glDepthFunc(GL_LESS) + + gltf_pbr_render(renderer, model, scene, view, proj, eye, light) + + glfwMakeContextCurrent(window) + glfwSwapBuffers(window) +} + +[export] +def shutdown() { + delete renderer + delete model + delete scene + glfwDestroyWindow(window) + glfwTerminate() +} + +var max_frame_limit = 0 + +def parse_args() { + let args <- get_command_line_arguments() + for (i in range(length(args) - 1)) { + if (args[i] == "--max-frames") { + max_frame_limit = to_int(args[i + 1]) + } + } +} - eval_main_loop() { - if (glfwWindowShouldClose(window) != 0) { - return false +// on web the wasm host auto-drives init/update/shutdown per frame and never calls main +[export] +def main() { + parse_args() + init() + var frame_count = 0 + while (glfwWindowShouldClose(window) == 0) { + update() + maybe_collect_gc() + frame_count ++ + if (max_frame_limit > 0 && frame_count >= max_frame_limit) { + break } - glfwPollEvents() - var display_w, display_h : int - glfwGetFramebufferSize(window, display_w, display_h) - let aspect = display_h != 0 ? float(display_w) / float(display_h) : 1. - - let cp = cos(camera_pitch) - let eye = center + float3(cp * sin(camera_yaw), sin(camera_pitch), cp * cos(camera_yaw)) * camera_distance - let view = look_at_rh(eye, center, float3(0., 1., 0.)) - let proj = perspective_rh_minus1_to_1(45.0f * PI / 180., aspect, radius * 0.05, radius * 60.0) - - glViewport(0, 0, display_w, display_h) - glClearColor(0.10, 0.10, 0.12, 1.0) - glClearDepth(1.0lf) - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) - glEnable(GL_DEPTH_TEST) - glDepthFunc(GL_LESS) - - gltf_pbr_render(renderer, model, scene, view, proj, eye, light) - - glfwMakeContextCurrent(window) - glfwSwapBuffers(window) - return true } + shutdown() } diff --git a/examples/opengl/11_hello_gltf_animation.das b/examples/opengl/11_hello_gltf_animation.das index e6669b48a7..0380c6fb14 100644 --- a/examples/opengl/11_hello_gltf_animation.das +++ b/examples/opengl/11_hello_gltf_animation.das @@ -1,13 +1,15 @@ options gen2 +options persistent_heap +options gc require glfw/glfw_boost require opengl/opengl_boost require gltf/gltf_boost require gltf/gltf_gl require gltf/gltf_pbr -require daslib/defer require math require daslib/math_boost require strings +require live/live_gc // dasGLTF — skinned glTF animation viewer (OpenGL). Loads a rigged, animated .glb, uploads it to GL, and // plays back its keyframe animation with GPU skinning under the same PBR shader as 10_hello_gltf. Orbit @@ -25,58 +27,55 @@ def model_path : string { return "{get_das_root()}/examples/gltf/media/Fox.glb" } +var window : GLFWwindow? +var scene : GltfScene +var model : GltfGlModel +var renderer : GltfPbrRenderer +var center : float3 +var radius = 0.001 +var camera_distance : float +var camera_yaw = 0.9 +var camera_pitch = 0.25 +var cursor_prev : float2 +var dragging = false +let light = GltfPbrLight() + +// animation playback state: cycle clips with SPACE; time advances per frame and loops per clip duration +var anim_count = 0 +var clip_index = 0 +var anim_time = 0.0 +var last_time = 0.0lf + [export] -def main { +def init() { if (glfwInit() == 0) { panic("can't init glfw") } - defer() { - glfwTerminate() - } glfwInitOpenGL(3, 3) - var window = glfwCreateWindow(1280, 720, "dasGLTF - Hello glTF Animation", null, null) + window = glfwCreateWindow(1280, 720, "dasGLTF - Hello glTF Animation", null, null) if (window == null) { panic("can't create window") } - defer() { - glfwDestroyWindow(window) - } glfwMakeContextCurrent(window) let path = model_path() - var scene <- load_gltf(path) - defer() { - delete scene - } + scene <- load_gltf(path) if (empty(scene.meshes)) { panic("no meshes loaded from {path}") } - var model <- upload_gltf(scene) - defer() { - delete model - } - var renderer <- gltf_pbr_create() - defer() { - delete renderer - } + model <- upload_gltf(scene) + renderer <- gltf_pbr_create() // auto-frame: orbit the world-space bounding sphere so any model (Fox is ~80 units) fills the view let bounds = gltf_scene_bounds(scene) - let center = (bounds._0 + bounds._1) * 0.5 - let radius = max(length(bounds._1 - bounds._0) * 0.5, 0.001) - - var camera_distance = radius * 2.2 - var camera_yaw = 0.9 - var camera_pitch = 0.25 - var cursor_prev = glfwGetCursorPos(window) - var dragging = false - let light = GltfPbrLight() - - // animation playback state: cycle clips with SPACE; time advances per frame and loops per clip duration - let anim_count = length(scene.animations) - var clip_index = 0 - var anim_time = 0.0 - var last_time = glfwGetTime() + center = (bounds._0 + bounds._1) * 0.5 + radius = max(length(bounds._1 - bounds._0) * 0.5, 0.001) + + camera_distance = radius * 2.2 + cursor_prev = glfwGetCursorPos(window) + + anim_count = length(scene.animations) + last_time = glfwGetTime() if (anim_count > 0) { print("dasGLTF: {anim_count} animation clip(s); press SPACE to cycle. Playing '{scene.animations[0].name}'\n") } else { @@ -84,22 +83,22 @@ def main { } unsafe { - glfwSetKeyCallback(window) @ capture(& clip_index, & anim_time, = anim_count) (w, key, scancode, action, mods) { + glfwSetKeyCallback(window) @(w, key, scancode, action, mods) { if (key == int(GLFW_KEY_SPACE) && action == int(GLFW_PRESS) && anim_count > 0) { clip_index = (clip_index + 1) % anim_count anim_time = 0.0 } } - glfwSetScrollCallback(window) @ capture(& camera_distance, & radius) (w, x, y) { + glfwSetScrollCallback(window) @(w, x, y) { camera_distance = clamp(camera_distance * (y > 0.0lf ? 0.9 : 1.1), radius * 1.1, radius * 40.0) } - glfwSetMouseButtonCallback(window) @ capture(& dragging, & cursor_prev) (w, b, a, m) { + glfwSetMouseButtonCallback(window) @(w, b, a, m) { if (b == int(GLFW_MOUSE_BUTTON_1)) { dragging = a == int(GLFW_PRESS) cursor_prev = glfwGetCursorPos(window) } } - glfwSetCursorPosCallback(window) @ capture(& dragging, & cursor_prev, & camera_yaw, & camera_pitch) (w, x, y) { + glfwSetCursorPosCallback(window) @(w, x, y) { let cur = glfwGetCursorPos(window) if (dragging) { let d = cur - cursor_prev @@ -109,45 +108,79 @@ def main { cursor_prev = cur } } +} - eval_main_loop() { - if (glfwWindowShouldClose(window) != 0) { - return false - } - glfwPollEvents() - - // advance + loop the active animation clip, then pose the skeleton for this frame - let now = glfwGetTime() - anim_time += float(now - last_time) - last_time = now - if (anim_count > 0) { - let dur = scene.animations[clip_index].duration - if (dur > 0.0 && anim_time >= dur) { - anim_time -= dur * floor(anim_time / dur) - } - evaluate_animation(scene, clip_index, anim_time) +[export] +def update() { + glfwPollEvents() + + // advance + loop the active animation clip, then pose the skeleton for this frame + let now = glfwGetTime() + anim_time += float(now - last_time) + last_time = now + if (anim_count > 0) { + let dur = scene.animations[clip_index].duration + if (dur > 0.0 && anim_time >= dur) { + anim_time -= dur * floor(anim_time / dur) } + evaluate_animation(scene, clip_index, anim_time) + } + + var display_w, display_h : int + glfwGetFramebufferSize(window, display_w, display_h) + let aspect = display_h != 0 ? float(display_w) / float(display_h) : 1. + + let cp = cos(camera_pitch) + let eye = center + float3(cp * sin(camera_yaw), sin(camera_pitch), cp * cos(camera_yaw)) * camera_distance + let view = look_at_rh(eye, center, float3(0., 1., 0.)) + let proj = perspective_rh_minus1_to_1(45.0f * PI / 180., aspect, radius * 0.05, radius * 60.0) + + glViewport(0, 0, display_w, display_h) + glClearColor(0.10, 0.10, 0.12, 1.0) + glClearDepth(1.0lf) + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) + glEnable(GL_DEPTH_TEST) + glDepthFunc(GL_LESS) - var display_w, display_h : int - glfwGetFramebufferSize(window, display_w, display_h) - let aspect = display_h != 0 ? float(display_w) / float(display_h) : 1. + gltf_pbr_render(renderer, model, scene, view, proj, eye, light) + + glfwMakeContextCurrent(window) + glfwSwapBuffers(window) +} - let cp = cos(camera_pitch) - let eye = center + float3(cp * sin(camera_yaw), sin(camera_pitch), cp * cos(camera_yaw)) * camera_distance - let view = look_at_rh(eye, center, float3(0., 1., 0.)) - let proj = perspective_rh_minus1_to_1(45.0f * PI / 180., aspect, radius * 0.05, radius * 60.0) +[export] +def shutdown() { + delete renderer + delete model + delete scene + glfwDestroyWindow(window) + glfwTerminate() +} - glViewport(0, 0, display_w, display_h) - glClearColor(0.10, 0.10, 0.12, 1.0) - glClearDepth(1.0lf) - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) - glEnable(GL_DEPTH_TEST) - glDepthFunc(GL_LESS) +var max_frame_limit = 0 - gltf_pbr_render(renderer, model, scene, view, proj, eye, light) +def parse_args() { + let args <- get_command_line_arguments() + for (i in range(length(args) - 1)) { + if (args[i] == "--max-frames") { + max_frame_limit = to_int(args[i + 1]) + } + } +} - glfwMakeContextCurrent(window) - glfwSwapBuffers(window) - return true +// on web the wasm host auto-drives init/update/shutdown per frame and never calls main +[export] +def main() { + parse_args() + init() + var frame_count = 0 + while (glfwWindowShouldClose(window) == 0) { + update() + maybe_collect_gc() + frame_count ++ + if (max_frame_limit > 0 && frame_count >= max_frame_limit) { + break + } } + shutdown() } From 7084a6872e15840ee698893cc72a10b89901e774 Mon Sep 17 00:00:00 2001 From: Boris Batkin Date: Thu, 30 Jul 2026 05:24:05 -0700 Subject: [PATCH 5/6] PR round 1: guard nullable window/lock-box in shutdown(), comment typos Copilot findings: shutdown() passed a nullable GLFWwindow? straight to glfwDestroyWindow. Guard + clear the handle (glfw_live.das live_destroy_window pattern) in all 14 converted files so shutdown is safe after partial init; hrtf's stats_box lock-box removal gets the same guard. glfwTerminate stays unconditional (documented safe pre-init). Also 4 comment typos. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01NcrnLGE9AEh8EAND3xYCRC --- examples/audio/hrtf/main.das | 10 ++++++++-- examples/opengl/01_hello_triangle.das | 5 ++++- examples/opengl/02_hello_image.das | 7 +++++-- examples/opengl/03_hello_cube.das | 5 ++++- examples/opengl/04_hello_render_to_texture.das | 5 ++++- examples/opengl/05_hello_compute.das | 7 +++++-- examples/opengl/06_hello_ttf.das | 5 ++++- examples/opengl/07_hello_gen.das | 5 ++++- examples/opengl/09_hello_mesh.das | 5 ++++- examples/opengl/10_hello_gltf.das | 5 ++++- examples/opengl/11_hello_gltf_animation.das | 5 ++++- examples/pathTracer/toy_path_tracer_opengl.das | 7 +++++-- examples/pathTracer/toy_path_tracer_opengl_hdr.das | 7 +++++-- examples/pathTracer/toy_pathtracer_opengl_basic.das | 5 ++++- 14 files changed, 64 insertions(+), 19 deletions(-) diff --git a/examples/audio/hrtf/main.das b/examples/audio/hrtf/main.das index 454f54a437..cbb2de960c 100644 --- a/examples/audio/hrtf/main.das +++ b/examples/audio/hrtf/main.das @@ -401,8 +401,14 @@ def update { // nolint:STYLE038 — linear per-frame UI flow [export] def shutdown { audio_system_finalize(asch.command, asch.next_sid) - unsafe(lock_box_remove(stats_box)) - glfwDestroyWindow(window) + if (stats_box != null) { + unsafe(lock_box_remove(stats_box)) + stats_box = null + } + if (window != null) { + glfwDestroyWindow(window) + window = null + } glfwTerminate() } diff --git a/examples/opengl/01_hello_triangle.das b/examples/opengl/01_hello_triangle.das index 334ca29bde..6035445442 100644 --- a/examples/opengl/01_hello_triangle.das +++ b/examples/opengl/01_hello_triangle.das @@ -90,7 +90,10 @@ def update() { [export] def shutdown() { - glfwDestroyWindow(window) + if (window != null) { + glfwDestroyWindow(window) + window = null + } glfwTerminate() } diff --git a/examples/opengl/02_hello_image.das b/examples/opengl/02_hello_image.das index 8241789e5a..65b1b2264f 100644 --- a/examples/opengl/02_hello_image.das +++ b/examples/opengl/02_hello_image.das @@ -106,14 +106,17 @@ def update() { glBindVertexArray(vao) glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo) glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, null) - // swap buffersa + // swap buffers glfwMakeContextCurrent(window) glfwSwapBuffers(window) } [export] def shutdown() { - glfwDestroyWindow(window) + if (window != null) { + glfwDestroyWindow(window) + window = null + } glfwTerminate() } diff --git a/examples/opengl/03_hello_cube.das b/examples/opengl/03_hello_cube.das index 16abc8451a..45118679af 100644 --- a/examples/opengl/03_hello_cube.das +++ b/examples/opengl/03_hello_cube.das @@ -157,7 +157,10 @@ def update() { [export] def shutdown() { - glfwDestroyWindow(window) + if (window != null) { + glfwDestroyWindow(window) + window = null + } glfwTerminate() } diff --git a/examples/opengl/04_hello_render_to_texture.das b/examples/opengl/04_hello_render_to_texture.das index c759fe0a32..71b5a42a96 100644 --- a/examples/opengl/04_hello_render_to_texture.das +++ b/examples/opengl/04_hello_render_to_texture.das @@ -164,7 +164,10 @@ def update() { [export] def shutdown() { - glfwDestroyWindow(window) + if (window != null) { + glfwDestroyWindow(window) + window = null + } glfwTerminate() } diff --git a/examples/opengl/05_hello_compute.das b/examples/opengl/05_hello_compute.das index 80428fab8b..57718872e7 100644 --- a/examples/opengl/05_hello_compute.das +++ b/examples/opengl/05_hello_compute.das @@ -148,7 +148,7 @@ def update() { e_mat.b_times_a = e_mat.b * e_mat.a e_mat.a_times_1234 = e_mat.a * float4(1, 2, 3, 4) e_mat.b_times_1234 = e_mat.b * float4(1, 2, 3, 4) - // compare CPU computations with GPU computaions + // compare CPU computations with GPU computations assert(e_mat.a_times_b == c_mat.a_times_b) assert(e_mat.b_times_a == c_mat.b_times_a) assert(e_mat.a_times_1234 == c_mat.a_times_1234) @@ -169,7 +169,10 @@ def update() { [export] def shutdown() { - glfwDestroyWindow(window) + if (window != null) { + glfwDestroyWindow(window) + window = null + } glfwTerminate() } diff --git a/examples/opengl/06_hello_ttf.das b/examples/opengl/06_hello_ttf.das index c5247ba5e4..a8d069ddfd 100644 --- a/examples/opengl/06_hello_ttf.das +++ b/examples/opengl/06_hello_ttf.das @@ -66,7 +66,10 @@ def update() { [export] def shutdown() { - glfwDestroyWindow(window) + if (window != null) { + glfwDestroyWindow(window) + window = null + } glfwTerminate() } diff --git a/examples/opengl/07_hello_gen.das b/examples/opengl/07_hello_gen.das index 801b69bd52..d377181d30 100644 --- a/examples/opengl/07_hello_gen.das +++ b/examples/opengl/07_hello_gen.das @@ -120,7 +120,10 @@ def update() { [export] def shutdown() { - glfwDestroyWindow(window) + if (window != null) { + glfwDestroyWindow(window) + window = null + } glfwTerminate() } diff --git a/examples/opengl/09_hello_mesh.das b/examples/opengl/09_hello_mesh.das index 432d725595..acaa10eea6 100644 --- a/examples/opengl/09_hello_mesh.das +++ b/examples/opengl/09_hello_mesh.das @@ -150,7 +150,10 @@ def update() { [export] def shutdown() { - glfwDestroyWindow(window) + if (window != null) { + glfwDestroyWindow(window) + window = null + } glfwTerminate() } diff --git a/examples/opengl/10_hello_gltf.das b/examples/opengl/10_hello_gltf.das index 8abdf42450..68ea43c6f3 100644 --- a/examples/opengl/10_hello_gltf.das +++ b/examples/opengl/10_hello_gltf.das @@ -119,7 +119,10 @@ def shutdown() { delete renderer delete model delete scene - glfwDestroyWindow(window) + if (window != null) { + glfwDestroyWindow(window) + window = null + } glfwTerminate() } diff --git a/examples/opengl/11_hello_gltf_animation.das b/examples/opengl/11_hello_gltf_animation.das index 0380c6fb14..9118a10c92 100644 --- a/examples/opengl/11_hello_gltf_animation.das +++ b/examples/opengl/11_hello_gltf_animation.das @@ -153,7 +153,10 @@ def shutdown() { delete renderer delete model delete scene - glfwDestroyWindow(window) + if (window != null) { + glfwDestroyWindow(window) + window = null + } glfwTerminate() } diff --git a/examples/pathTracer/toy_path_tracer_opengl.das b/examples/pathTracer/toy_path_tracer_opengl.das index 20989a5c94..c5d17d1db4 100644 --- a/examples/pathTracer/toy_path_tracer_opengl.das +++ b/examples/pathTracer/toy_path_tracer_opengl.das @@ -134,7 +134,7 @@ def update { glfwPollEvents() var display_w, display_h : int glfwGetFramebufferSize(window, display_w, display_h) - // copmute next iteration of tracing + // compute next iteration of tracing glUseProgram(compute_program) c_destTex := texture compute_effect_bind_uniform(compute_program) @@ -157,7 +157,10 @@ def update { [export] def shutdown { - glfwDestroyWindow(window) + if (window != null) { + glfwDestroyWindow(window) + window = null + } glfwTerminate() } diff --git a/examples/pathTracer/toy_path_tracer_opengl_hdr.das b/examples/pathTracer/toy_path_tracer_opengl_hdr.das index 7422c39239..fbb61372a1 100644 --- a/examples/pathTracer/toy_path_tracer_opengl_hdr.das +++ b/examples/pathTracer/toy_path_tracer_opengl_hdr.das @@ -228,7 +228,7 @@ def update { glfwPollEvents() var display_w, display_h : int glfwGetFramebufferSize(window, display_w, display_h) - // copmute next iteration of tracing + // compute next iteration of tracing glUseProgram(compute_program) c_destTex := texture compute_effect_bind_uniform(compute_program) @@ -287,7 +287,10 @@ def update { [export] def shutdown { - glfwDestroyWindow(window) + if (window != null) { + glfwDestroyWindow(window) + window = null + } glfwTerminate() } diff --git a/examples/pathTracer/toy_pathtracer_opengl_basic.das b/examples/pathTracer/toy_pathtracer_opengl_basic.das index 221a4e6d2d..36b8dc2fdc 100644 --- a/examples/pathTracer/toy_pathtracer_opengl_basic.das +++ b/examples/pathTracer/toy_pathtracer_opengl_basic.das @@ -126,7 +126,10 @@ def update { [export] def shutdown { - glfwDestroyWindow(window) + if (window != null) { + glfwDestroyWindow(window) + window = null + } glfwTerminate() } From 6ce3f858d5a8bc4e33fc66b3264cd3704b686b75 Mon Sep 17 00:00:00 2001 From: Boris Batkin Date: Thu, 30 Jul 2026 05:31:10 -0700 Subject: [PATCH 6/6] CI: order imgui superbuild statics after the GLFW3 ExternalProject libImguiApp & co. take GLFW_INCLUDE_DIR (the GLFW3 ExternalProject's $/include install tree) but had no dependency edge on it, so Ninja could compile module_imgui_app.cpp before the 3.4 headers were staged and fall back to the system GLFW 3.3 (no GLFWallocator) - hit on the linux Debug lane. Same ADD_DEPENDENCIES edge dasGlfw's own libs and the imguiApp shared twin already carry. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01NcrnLGE9AEh8EAND3xYCRC --- modules/dasImgui/CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/dasImgui/CMakeLists.txt b/modules/dasImgui/CMakeLists.txt index 6eda7715eb..065e4bc00f 100644 --- a/modules/dasImgui/CMakeLists.txt +++ b/modules/dasImgui/CMakeLists.txt @@ -296,6 +296,11 @@ IF(COMMAND ADD_MODULE_CPP) ENDMACRO() FOREACH(_t ${_IMGUI_SUPER_TARGETS}) SETUP_IMGUI(${_t}) + # order after the GLFW3 ExternalProject: GLFW_INCLUDE_DIR is its install + # tree; compiled too early, the include falls back to a system GLFW 3.3 + IF(TARGET GLFW3) + ADD_DEPENDENCIES(${_t} GLFW3) + ENDIF() ENDFOREACH() # the imgui core symbols live in libDasModuleImgui; the app backends use them TARGET_LINK_LIBRARIES(libImguiApp PUBLIC libDasModuleImgui)