From 084a7c6839b95fbdff8f4c96a76f62d68b70c003 Mon Sep 17 00:00:00 2001 From: Shokunin <30949000+tetrisgm@users.noreply.github.com> Date: Tue, 6 Oct 2026 05:06:07 -0700 Subject: [PATCH] Present thread: one owned swap call; frame-gen fixture builds on MinGW Both swap paths go through gl_window_swap. The temporal-blending ownership guard allows the present thread's context (pt_begin only). The frame generation fixture used setenv, which MinGW lacks. Co-Authored-By: Claude Opus 5.5 --- runtime/src/gpu_gl_renderer.c | 8 ++++++-- .../tests/test_frame_interpolation_context_ownership.py | 8 +++++++- runtime/tests/test_gl_frame_gen.c | 4 ++++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/runtime/src/gpu_gl_renderer.c b/runtime/src/gpu_gl_renderer.c index cd1071df..d40119dc 100644 --- a/runtime/src/gpu_gl_renderer.c +++ b/runtime/src/gpu_gl_renderer.c @@ -8301,6 +8301,10 @@ static int pt_cb_ctx(void *user, int current) { return 1; } +/* The one owned swap call: the composing thread's (direct) or the present + * thread's. */ +static void gl_window_swap(void) { SDL_GL_SwapWindow(s_win); } + static void *pt_cb_present(void *user, int slot, void *ready) { (void)user; if (s_ptp_interval_gen != s_pt_interval_gen) { @@ -8335,7 +8339,7 @@ static void *pt_cb_present(void *user, int slot, void *ready) { GL_PRESENT_THREAD_TEST_HOOK(slot); #endif void *done = p_glFenceSync(PT_SYNC_GPU_COMMANDS_COMPLETE, 0); - SDL_GL_SwapWindow(s_win); + gl_window_swap(); return done; } @@ -8412,7 +8416,7 @@ static void pt_end(void) { /* The swap: direct, or queued to the present thread. */ static void gl_present_swap(void) { - if (!s_pt_on) { SDL_GL_SwapWindow(s_win); return; } + if (!s_pt_on) { gl_window_swap(); return; } (void)pt_target_fbo(); /* a frame that never bound 0 still has its slot */ void *ready = p_glFenceSync(PT_SYNC_GPU_COMMANDS_COMPLETE, 0); glFlush(); diff --git a/runtime/tests/test_frame_interpolation_context_ownership.py b/runtime/tests/test_frame_interpolation_context_ownership.py index ca649af6..d1257630 100644 --- a/runtime/tests/test_frame_interpolation_context_ownership.py +++ b/runtime/tests/test_frame_interpolation_context_ownership.py @@ -9,9 +9,15 @@ RENDERER = (ROOT / "runtime" / "src" / "gpu_gl_renderer.c").read_text( encoding="utf-8" ) -assert RENDERER.count("SDL_GL_CreateContext(") == 1, ( +# The only second context is the present thread's ([video] present_thread, +# docs/RENDER_THREAD.md), created in pt_begin under the render thread, which +# frame interpolation makes ineligible. +assert RENDERER.count("SDL_GL_CreateContext(") == 2, ( "temporal blending must not create a second GL context" ) +_pt = RENDERER[RENDERER.index("static int pt_begin(void)"):] +_pt = _pt[:_pt.index("\n}\n")] +assert "SDL_GL_CreateContext(" in _pt, "the second GL context belongs to the present thread" assert "SDL_CreateThread(" not in RENDERER, ( "temporal blending must not create a presentation thread" ) diff --git a/runtime/tests/test_gl_frame_gen.c b/runtime/tests/test_gl_frame_gen.c index efc8d5a4..459d5d58 100644 --- a/runtime/tests/test_gl_frame_gen.c +++ b/runtime/tests/test_gl_frame_gen.c @@ -254,7 +254,11 @@ int main(int argc, char **argv) { check(gl_renderer_render_thread_start(2) == 1, "render thread started"); if (pt_mode) check(gl_renderer_present_thread_active(), "present thread started"); if (fg) { +#ifdef _WIN32 + _putenv("PSX_FRAME_GEN_FORCE=1"); +#else setenv("PSX_FRAME_GEN_FORCE", "1", 1); +#endif gl_renderer_set_frame_generation(1); check(gl_renderer_frame_generation() == 1, "frame generation on"); }