Drain and reset session HUD callbacks

This commit is contained in:
Matthew Stanley
2026-10-22 17:03:06 -07:00
parent 0127fcfc73
commit d383ef210b
7 changed files with 86 additions and 3 deletions
+1
View File
@@ -1396,6 +1396,7 @@ if(BUILD_TESTING)
add_executable(mod_runtime_test
src/mod_immediate.c
src/host_overlay.c
tests/test_mod_runtime.cpp
src/iso_reader_c.cpp
src/host_span_ring.c
+4 -2
View File
@@ -49,8 +49,10 @@ void psx_host_overlay_draw(int width, int height); /* renderer side */
/* ---- Game HUD layer ---------------------------------------------------------
* A title's own heads-up display, drawn once per presented frame (real and
* generated) under the in-game menu, same thread and GL rules as the overlay
* callback above. Set once (title activation) and kept for the session; NULL
* unregisters (no wait: the registrant keeps fn and ctx alive for the session). */
* callback above. OpenGL only; Vulkan and the SDL renderer never invoke it.
* Replacing or NULL unregistering drains prior calls as for the overlay above;
* the owner may then release its old context. Registration is session-scoped:
* activation reset and GL shutdown clear it. Register again on activation. */
void psx_host_set_hud_draw_cb(PsxHostOverlayDrawFn fn, void *ctx);
void psx_host_hud_draw(int width, int height); /* renderer side */
+1
View File
@@ -6550,6 +6550,7 @@ static void pass_resources_release(void);
static void present_image_ring_gl_forget(void);
void gl_renderer_shutdown(void) {
gl_renderer_render_thread_stop(); /* drains; the context comes back to this thread */
psx_host_set_hud_draw_cb(NULL, NULL);
/* After the worker has stopped (it presents, and captures, on its own
* thread) and before the context dies: forget the ring's GL names. */
present_image_ring_gl_forget();
+33 -1
View File
@@ -104,15 +104,47 @@ void psx_host_overlay_draw(int width, int height) {
/* ---- Game HUD layer ------------------------------------------------------- */
static PsxHostOverlayDrawFn s_hud_fn = NULL;
static void *s_hud_ctx = NULL;
static atomic_flag s_hud_lock = ATOMIC_FLAG_INIT;
static atomic_int s_hud_inflight = 0;
#if defined(_MSC_VER)
static __declspec(thread) int s_in_hud = 0;
#else
static _Thread_local int s_in_hud = 0;
#endif
static void hud_lock(void) {
while (atomic_flag_test_and_set_explicit(&s_hud_lock, memory_order_acquire))
draw_yield();
}
static void hud_unlock(void) {
atomic_flag_clear_explicit(&s_hud_lock, memory_order_release);
}
void psx_host_set_hud_draw_cb(PsxHostOverlayDrawFn fn, void *ctx) {
hud_lock();
s_hud_ctx = ctx;
s_hud_fn = fn;
hud_unlock();
/* Match the overlay lifetime contract without allocating callback records.
* The single GL owner may unregister itself; its current call then ends
* before another frame can consume the replacement. */
if (!s_in_hud)
while (atomic_load(&s_hud_inflight) > 0) draw_yield();
}
void psx_host_hud_draw(int width, int height) {
if (width <= 0 || height <= 0) return;
hud_lock();
PsxHostOverlayDrawFn fn = s_hud_fn;
if (fn && width > 0 && height > 0) fn(width, height, s_hud_ctx);
void *ctx = s_hud_ctx;
atomic_fetch_add(&s_hud_inflight, 1);
hud_unlock();
if (fn) {
s_in_hud = 1;
fn(width, height, ctx);
s_in_hud = 0;
}
atomic_fetch_sub(&s_hud_inflight, 1);
}
/* ---- P3: input sink -------------------------------------------------------- */
+3
View File
@@ -1,5 +1,6 @@
#include "mod_runtime.h"
#include "host_launch_timing.h"
#include "host_overlay.h"
#include "cpu_state.h"
#include "disc_path.h"
@@ -2963,6 +2964,8 @@ bool mod_runtime_read_disc_file_sectors(const std::string& path, uint32_t max_by
extern "C" void mod_runtime_activate_plugins(void) {
using namespace PSXRecompV4;
HostLaunchTimingScope timing(HOST_LAUNCH_PLUGIN_ACTIVATION);
/* Drain session-owned presentation callbacks before their contexts reset. */
psx_host_set_hud_draw_cb(nullptr, nullptr);
mod_reset_plugin_sessions();
RuntimeMods& s = state();
s.ticket_ready = false;
+38
View File
@@ -75,6 +75,29 @@ static void* life_reader(void* arg) {
while (!atomic_load(&g_stop)) psx_host_overlay_draw(64, 64);
return NULL;
}
static void* hud_life_reader(void* arg) {
(void)arg;
while (!atomic_load(&g_stop)) psx_host_hud_draw(64, 64);
return NULL;
}
static void test_hud_lifetime(void) {
pthread_t t;
atomic_store(&g_stop, 0);
pthread_create(&t, NULL, hud_life_reader, NULL);
for (int round = 0; round < 100; ++round) {
LifeCtx* c = (LifeCtx*)calloc(1, sizeof(*c));
atomic_store(&c->alive, 1);
psx_host_set_hud_draw_cb(life_draw, c);
while (atomic_load(&c->calls) < 2) {}
/* Also models the activation-reset / renderer-shutdown clear. */
psx_host_set_hud_draw_cb(NULL, NULL);
atomic_store(&c->alive, 0);
CHECK(atomic_load(&c->used_dead) == 0);
free(c);
}
atomic_store(&g_stop, 1);
pthread_join(t, NULL);
}
static void test_draw_lifetime(void) {
pthread_t t;
atomic_store(&g_stop, 0);
@@ -99,6 +122,7 @@ static void test_draw_lifetime(void) {
}
#else
static void test_draw_lifetime(void) {}
static void test_hud_lifetime(void) {}
#endif
/* Changing the callback from inside it must not deadlock. */
@@ -114,6 +138,18 @@ static void test_draw_reentrant(void) {
psx_host_overlay_draw(64, 64);
CHECK(n == 1 && !psx_host_overlay_has_draw());
}
static void hud_self_unregister(int w, int h, void* p) {
(void)w; (void)h;
++*(int*)p;
psx_host_set_hud_draw_cb(NULL, NULL);
}
static void test_hud_reentrant(void) {
int calls = 0;
psx_host_set_hud_draw_cb(hud_self_unregister, &calls);
psx_host_hud_draw(64, 64);
psx_host_hud_draw(64, 64);
CHECK(calls == 1);
}
static int sunk = 0;
static int sink(const void *ev, void *ctx) { (void)ctx; sunk++; return *(const int *)ev == 1; }
@@ -237,6 +273,8 @@ static void test_cmdline_quote(void) {
}
int main(void) {
test_hud_lifetime();
test_hud_reentrant();
test_draw_lifetime();
test_draw_reentrant();
test_cmdline_quote();
+6
View File
@@ -1,6 +1,7 @@
#include "mod_runtime.h"
#include "mod_netplay.h"
#include "host_launch_timing.h"
#include "host_overlay.h"
#include "host_file_identity.h"
#include "mod_packages.h"
#include "mod_plugins.h"
@@ -107,6 +108,8 @@ extern "C" uint32_t psx_mod_gpu_dma_memory_alloc(uint32_t, uint32_t) {
}
extern "C" void psx_ram_reset_size_request(void) {}
static int hd_shutdown_calls, hd_configure_calls, hd_dump_value;
static int hud_calls;
static void old_session_hud(int, int, void*) { ++hud_calls; }
static int hd_replacements_value, hd_reload_calls;
static std::string hd_root;
extern "C" void gpu_hd_textures_shutdown(void) { ++hd_shutdown_calls; hd_root.clear(); }
@@ -724,7 +727,10 @@ int main(int argc, char** argv) {
psx_mod_function_entry(&entry_cpu, 0x80003000u);
check(g_psx_mod_function_entry_hooks == 0 && active_entry_hits == 0,
"function-entry hooks must not run before plugin activation");
psx_host_set_hud_draw_cb(old_session_hud, nullptr);
mod_runtime_activate_plugins();
psx_host_hud_draw(64, 64);
check(hud_calls == 0, "activation clears the prior session HUD before resetting plugins");
check(activation_calls == 1,
"resolved trusted plugin must activate before runtime startup");
entry_cpu.pc = 0x80004000u;