From 8b003a591dfbb06a09eaee1a88822acdb02e2c80 Mon Sep 17 00:00:00 2001 From: Shokunin <30949000+tetrisgm@users.noreply.github.com> Date: Fri, 9 Oct 2026 00:25:21 -0700 Subject: [PATCH] Overlay draw callback: publish fn+ctx atomically; link it in GL fixtures Review (#599): fn and ctx were two separate stores read on the render thread, so an unregister could pair a live fn with a NULL ctx. They are now one immutable record behind an acquire/release atomic pointer. The GL fixture runners compile a fixed source list; gpu_gl_renderer.c now calls psx_host_overlay_draw, so every run_gl_*.py links host_overlay.c. Co-Authored-By: Claude Opus 5.5 --- runtime/src/host_overlay.c | 36 ++++++++++++++++++------ runtime/tests/run_gl_frame_gen.py | 3 ++ runtime/tests/run_gl_readback_region.py | 3 ++ runtime/tests/run_gl_render_thread.py | 3 ++ runtime/tests/run_gl_scale_invariance.py | 3 ++ runtime/tests/run_gl_texture_filter.py | 3 ++ 6 files changed, 42 insertions(+), 9 deletions(-) diff --git a/runtime/src/host_overlay.c b/runtime/src/host_overlay.c index 19208cde..9374c11e 100644 --- a/runtime/src/host_overlay.c +++ b/runtime/src/host_overlay.c @@ -1,7 +1,9 @@ /* host_overlay.c -- see host_overlay.h. */ #include "host_overlay.h" +#include #include +#include /* ---- P1: host pause ------------------------------------------------------ */ #define PSX_HOST_PAUSE_MAX 8 @@ -29,19 +31,35 @@ const char *psx_host_pause_reason(void) { void psx_host_pause_set_refuse_probe(PsxHostPauseRefuseFn fn) { s_pause_refuse = fn; } /* ---- P2: overlay draw callback -------------------------------------------- */ -/* Written on the main thread before the overlay can open, read on the GL - * thread; a pointer-sized store, and registration happens once at startup. */ -static PsxHostOverlayDrawFn volatile s_draw_fn = NULL; -static void *volatile s_draw_ctx = NULL; +/* Registered on the main thread, read on the render / present thread. The + * function and its context are published together as one immutable record + * behind an atomic pointer (release on store, acquire on load), so a reader + * can never pair a live function with a stale or NULL context. Records are + * never freed: registration happens a handful of times per process, and a + * reader may still hold the previous record when it is replaced. */ +typedef struct { + PsxHostOverlayDrawFn fn; + void *ctx; +} PsxHostOverlayDraw; + +static _Atomic(PsxHostOverlayDraw *) s_draw = NULL; void psx_host_overlay_set_draw_cb(PsxHostOverlayDrawFn fn, void *ctx) { - s_draw_ctx = ctx; - s_draw_fn = fn; + PsxHostOverlayDraw *rec = NULL; + if (fn) { + rec = (PsxHostOverlayDraw *)malloc(sizeof(*rec)); + if (!rec) return; + rec->fn = fn; + rec->ctx = ctx; + } + atomic_store_explicit(&s_draw, rec, memory_order_release); } -int psx_host_overlay_has_draw(void) { return s_draw_fn != NULL; } +int psx_host_overlay_has_draw(void) { + return atomic_load_explicit(&s_draw, memory_order_acquire) != NULL; +} void psx_host_overlay_draw(int width, int height) { - PsxHostOverlayDrawFn fn = s_draw_fn; - if (fn && width > 0 && height > 0) fn(width, height, s_draw_ctx); + const PsxHostOverlayDraw *rec = atomic_load_explicit(&s_draw, memory_order_acquire); + if (rec && rec->fn && width > 0 && height > 0) rec->fn(width, height, rec->ctx); } diff --git a/runtime/tests/run_gl_frame_gen.py b/runtime/tests/run_gl_frame_gen.py index 215ae438..e57ec21d 100644 --- a/runtime/tests/run_gl_frame_gen.py +++ b/runtime/tests/run_gl_frame_gen.py @@ -95,6 +95,9 @@ def main(): if WINDOWS: sections.append("-flto") objs = [] + # The renderer calls the in-game overlay hook (host_overlay.c) at present. + if (src / "host_overlay.c").exists(): + sources.append(("hov", src / "host_overlay.c")) for name, path in sources: o = dest / (name + ".o") r = run([args.cc, "-std=gnu11", "-O1", "-DPSX_SDL3=1", "-DPSX_NO_DEBUG_TOOLS=1", diff --git a/runtime/tests/run_gl_readback_region.py b/runtime/tests/run_gl_readback_region.py index fbc0f768..39c18680 100644 --- a/runtime/tests/run_gl_readback_region.py +++ b/runtime/tests/run_gl_readback_region.py @@ -77,6 +77,9 @@ def main(): sources.append(("pth", framework / "runtime/src/present_thread.c")) if (framework / "runtime/src/frame_gen.c").exists(): sources.append(("fg", framework / "runtime/src/frame_gen.c")) + # The renderer calls the in-game overlay hook (host_overlay.c) at present. + if (framework / "runtime/src" / "host_overlay.c").exists(): + sources.append(("hov", framework / "runtime/src" / "host_overlay.c")) for name, source in sources: if run([compiler / "gcc.exe", "-std=c11", "-O2", "-flto", "-DPSX_SDL3=1", "-DPSX_NO_DEBUG_TOOLS=1", *includes, "-c", source, diff --git a/runtime/tests/run_gl_render_thread.py b/runtime/tests/run_gl_render_thread.py index 7bd3c578..cf088417 100644 --- a/runtime/tests/run_gl_render_thread.py +++ b/runtime/tests/run_gl_render_thread.py @@ -94,6 +94,9 @@ def main(): if WINDOWS: sections.append("-flto") objs = [] + # The renderer calls the in-game overlay hook (host_overlay.c) at present. + if (src / "host_overlay.c").exists(): + sources.append(("hov", src / "host_overlay.c")) for name, path in sources: o = dest / (name + ".o") r = run([args.cc, "-std=gnu11", "-O1", "-DPSX_SDL3=1", "-DPSX_NO_DEBUG_TOOLS=1", diff --git a/runtime/tests/run_gl_scale_invariance.py b/runtime/tests/run_gl_scale_invariance.py index b1d09cab..d0e055c7 100644 --- a/runtime/tests/run_gl_scale_invariance.py +++ b/runtime/tests/run_gl_scale_invariance.py @@ -154,6 +154,9 @@ def main(): if passes: sections.append("-DPSX_TEST_RENDER_PASSES=1") objs = [] + # The renderer calls the in-game overlay hook (host_overlay.c) at present. + if (framework / "runtime/src" / "host_overlay.c").exists(): + sources.append(("hov", framework / "runtime/src" / "host_overlay.c")) for name, src in sources: o = dest / (name + ".o") r = run([args.cc, "-std=gnu11", "-O1", "-DPSX_SDL3=1", "-DPSX_NO_DEBUG_TOOLS=1", diff --git a/runtime/tests/run_gl_texture_filter.py b/runtime/tests/run_gl_texture_filter.py index 75c379a4..1c0a4291 100644 --- a/runtime/tests/run_gl_texture_filter.py +++ b/runtime/tests/run_gl_texture_filter.py @@ -52,6 +52,9 @@ def main(): if (fw/'runtime/src/frame_gen.c').exists(): sources.append(('fg',fw/'runtime/src/frame_gen.c')) objects=[] + # The renderer calls the in-game overlay hook (host_overlay.c) at present. + if (fw/'runtime/src' / "host_overlay.c").exists(): + sources.append(("hov", fw/'runtime/src' / "host_overlay.c")) for name,source in sources: obj=out/(name+'.o'); run([args.cc,*flags,'-c',source,'-o',obj]); objects.append(obj) if args.hd_pack: