diff --git a/runtime/src/gpu_gl_renderer.c b/runtime/src/gpu_gl_renderer.c index 1ec7d79c..a6a40cf6 100644 --- a/runtime/src/gpu_gl_renderer.c +++ b/runtime/src/gpu_gl_renderer.c @@ -429,6 +429,34 @@ static int load_modern_gl(void) { static SDL_Window *s_win = NULL; static uint64_t s_window_owner; static _Atomic uint64_t s_drawable_extent; +static uint64_t s_drawable_queries; +#if defined(PSX_SDL3) +static _Atomic uint32_t s_drawable_window_id; +static int s_drawable_watch; +static bool SDLCALL drawable_event_watch(void *unused, SDL_Event *event) { + (void)unused; + if (event->type == SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED && + event->window.windowID == atomic_load_explicit(&s_drawable_window_id, memory_order_relaxed) && + event->window.data1 >= 0 && event->window.data2 >= 0) + atomic_store_explicit(&s_drawable_extent, + ((uint64_t)(uint32_t)event->window.data1 << 32) | (uint32_t)event->window.data2, + memory_order_release); + return true; +} +#endif +static int drawable_events_enabled(void) { +#if defined(PSX_SDL3) + if (!s_drawable_watch) return 0; + static int enabled = -1; + if (enabled < 0) { + const char *e = getenv("PSX_GL_DRAWABLE_EVENTS"); + enabled = !(e && e[0] == '0'); + } + return enabled; +#else + return 0; /* SDL2 resize events are logical, not drawable-pixel extents. */ +#endif +} static uint64_t window_thread_id(void) { #if defined(PSX_SDL3) return (uint64_t)SDL_GetCurrentThreadID(); @@ -436,17 +464,19 @@ static uint64_t window_thread_id(void) { return (uint64_t)SDL_ThreadID(); #endif } -/* SDL3's pixel-size query is main-thread-only. Publish one consistent pair - * before each frame handoff; GL workers never enter the window-system query. */ +/* Query only at context creation (or in the legacy control). SDL3 pixel-size + * events publish one consistent pair without taking SDL's window-properties + * lock, which the swapping thread can hold. No worker queries the window. */ static void gl_drawable_refresh(void) { if (!s_win || window_thread_id() != s_window_owner) return; int w = 0, h = 0; SDL_GL_GetDrawableSize(s_win, &w, &h); + s_drawable_queries++; atomic_store_explicit(&s_drawable_extent, ((uint64_t)(uint32_t)w << 32) | (uint32_t)h, memory_order_release); } static void gl_drawable_size(int *w, int *h) { - gl_drawable_refresh(); + if (!drawable_events_enabled()) gl_drawable_refresh(); uint64_t extent = atomic_load_explicit(&s_drawable_extent, memory_order_acquire); if (w) *w = (int)(extent >> 32); if (h) *h = (int)(uint32_t)extent; @@ -6791,6 +6821,11 @@ int gl_renderer_init_context(SDL_Window *win) { s_win = win; texel_cache_reset(); s_window_owner = window_thread_id(); +#if defined(PSX_SDL3) + if (s_drawable_watch) SDL_RemoveEventWatch(drawable_event_watch, NULL); + atomic_store(&s_drawable_window_id, SDL_GetWindowID(win)); + s_drawable_watch = SDL_AddEventWatch(drawable_event_watch, NULL) ? 1 : 0; +#endif gl_drawable_refresh(); s_present_w = 0; s_present_h = 0; @@ -6928,6 +6963,11 @@ void gl_renderer_work_stats(GlRenderWorkStats *out) { 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); +#if defined(PSX_SDL3) + if (s_drawable_watch) SDL_RemoveEventWatch(drawable_event_watch, NULL); + s_drawable_watch = 0; + atomic_store(&s_drawable_window_id, 0); +#endif const char *perf = getenv("PSX_RUNTIME_PERF_DIAG"); if (s_ctx && perf && perf[0] && perf[0] != '0') fprintf(stdout, "psxrecomp: GL work totals: queue_uploads=%llu replay_draws=%llu " @@ -14996,7 +15036,7 @@ int gl_renderer_render_thread_active(void) { return s_rth_on; } * to the render thread when the frame may run asynchronously, take it when it * may not, then close the frame (the in-flight bound applies here). */ void gl_renderer_render_thread_frame_boundary(void) { - gl_drawable_refresh(); + if (!drawable_events_enabled()) gl_drawable_refresh(); if (!s_rth_on) return; fg_note_guest_frame(); if (rt_held()) { diff --git a/runtime/tests/run_gl_render_thread.py b/runtime/tests/run_gl_render_thread.py index d0015f50..20b85216 100644 --- a/runtime/tests/run_gl_render_thread.py +++ b/runtime/tests/run_gl_render_thread.py @@ -157,21 +157,24 @@ def main(): ok = False runs[threaded] = p if args.wide_present: - for margin, coverage, state, upload, bloom, cache, layers, copy, identity in ((0,0,0,0,0,0,256,1,1), (0,0,1,1,1,1,256,1,1), (0,1,1,1,1,1,256,1,1), (1,0,1,1,1,1,256,1,1), (1,1,1,0,1,1,256,1,1), (1,1,1,1,0,1,256,1,1), (1,1,1,1,1,0,256,1,1), (1,1,1,1,1,1,4,1,1), (1,1,1,1,1,1,256,0,1), (1,1,1,1,1,1,256,1,0)): - reference = run([probe, s, threaded, args.frames, *mode], - {"PSX_GL_WIDE_MARGIN_SCISSOR": str(margin), - "PSX_GL_COVERAGE_FAST": str(coverage), - "PSX_GL_STATE_QUERY_FAST": str(state), - "PSX_GL_QUEUE_UPLOAD_FAST": str(upload), - "PSX_GL_BLOOM_FUSE_EXTEND": str(bloom), - "PSX_GL_TEXEL_CACHE": str(cache), - "PSX_GL_TEXEL_CACHE_LAYERS": str(layers), - "PSX_GL_HIW_COPY_FAST": str(copy), - "PSX_GL_IDENTITY_COPY_FAST": str(identity)}) + flags = ( + "PSX_GL_WIDE_MARGIN_SCISSOR", "PSX_GL_COVERAGE_FAST", + "PSX_GL_STATE_QUERY_FAST", "PSX_GL_QUEUE_UPLOAD_FAST", + "PSX_GL_BLOOM_FUSE_EXTEND", "PSX_GL_TEXEL_CACHE", + "PSX_GL_HIW_COPY_FAST", "PSX_GL_IDENTITY_COPY_FAST", + "PSX_GL_DRAWABLE_EVENTS", "PSX_RENDER_QUEUE_BATCH") + controls = [{flag: "0" for flag in flags}] + controls += [{flag: "0"} for flag in flags] + controls += [{"PSX_GL_TEXEL_CACHE_LAYERS": "4"}] + for control in controls: + overrides = {flag: "1" for flag in flags} + overrides.update(control) + reference = run([probe, s, threaded, args.frames, *mode], overrides) baseline = parse(reference.stdout) if reference.returncode or baseline["failures"] != 0 or any(baseline[k] != p[k] for k in KEYS): - print(f"FAIL scale {s}: margin={margin} coverage={coverage} changed rendered output", baseline, p) + print(f"FAIL scale {s}: controls {control} changed rendered output", baseline, p) ok = False + for k in KEYS: if runs[0][k] != runs[1][k]: print(f"FAIL scale {s}: {k} differs with the render thread on " diff --git a/runtime/tests/test_gl_render_thread.c b/runtime/tests/test_gl_render_thread.c index ccc91cc0..a1a410dc 100644 --- a/runtime/tests/test_gl_render_thread.c +++ b/runtime/tests/test_gl_render_thread.c @@ -376,6 +376,7 @@ int main(int argc, char **argv) { } static const int levels[] = { 2, 1, 3, 1, 4, 2 }; int nsteps = 0, level = scale; + const uint64_t drawable_queries_before = s_drawable_queries; for (int f = 0; f < frames; f++) { frame(f, (f & 1) ? 512 : 0); if (dynres && f % 7 == 6) { /* includes frames held at a sync point */ @@ -507,8 +508,10 @@ int main(int argc, char **argv) { check(st.acquires >= 2 && st.acquires <= (uint64_t)frames + 4, "acquires bounded"); check(st.bytes > ((uint64_t)32 << 20), "ring wrapped (more than its size recorded)"); } - /* A resize is published at the main-thread handoff and is visible to a - * worker without calling SDL's main-thread-only pixel-size query. */ + if (drawable_events_enabled()) + check(s_drawable_queries == drawable_queries_before, "no per-frame drawable queries"); + /* Pixel-size events publish resizes without querying the window on a + * worker or taking its property lock on every frame boundary. */ for (int k = 0; k < 2; ++k) { SDL_SetWindowSize(win, 400 + k * 80, 300 + k * 60); SDL_PumpEvents();