mirror of
https://github.com/jkdansereau/goldeneye-pc-port.git
synced 2026-10-23 15:52:42 +02:00
D344: orderly quit so exit never lands inside the GL driver; GE_QUITFRAME
Quit (quit event, window close, Alt+F4, from either SDL pump) used to exit(0) immediately while the render thread could be mid-frame in the driver: the likely trigger of the host's 0x119 bugchecks. Now quits are requested; the render thread glFinish()es, unbinds the context and parks at a frame boundary, and the host exits after (max 2 s). GE_QUITFRAME=<n> gives the harness a clean self-quit; golden_gate.sh uses it.
This commit is contained in:
1 parent
c07af11fae
commit
461ea871c2
5 files changed
+123
-11
No files matched your search
@@ -16,6 +16,8 @@ void gfx_sdl_make_context_current(void);
|
||||
/* Release the GL context from the calling (host main) thread after init, so
|
||||
* the game's scheduler thread can bind it per frame (see gfx_sdl2.cpp). */
|
||||
void gfx_sdl_release_context(void);
|
||||
/* D344: finish queued GL work and unbind the context (render thread, quit). */
|
||||
void gfx_sdl_park_for_exit(void);
|
||||
|
||||
/* Refresh the cached drawable size after a window resize (called by the
|
||||
* host thread's event pump). */
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
#include "gfx_screen_config.h"
|
||||
#include "input.h"
|
||||
|
||||
extern "C" void videoRequestQuit(const char *why); // port/src/video.c (D344)
|
||||
|
||||
static SDL_Window* wnd;
|
||||
static SDL_GLContext ctx;
|
||||
static SDL_Renderer* renderer;
|
||||
@@ -319,7 +321,9 @@ static void gfx_sdl_handle_events(void) {
|
||||
} else if (event.key.keysym.sym == SDLK_F4 && (event.key.keysym.mod & KMOD_ALT)) {
|
||||
// D145: Alt+F4 quits; bare ESC no longer does (it is the
|
||||
// menu "back" key -- see port/src/video.c / input.c).
|
||||
exit(0);
|
||||
// D344: request, don't exit(): this is the render thread,
|
||||
// mid-frame. It parks at the frame's end; the host exits.
|
||||
videoRequestQuit("Alt+F4 (render pump)");
|
||||
} else if (event.key.keysym.sym == SDLK_ESCAPE && !event.key.repeat) {
|
||||
// WI-1: this render-thread pump and port/src/video.c's
|
||||
// host-thread pump both drain the same SDL queue, so either
|
||||
@@ -345,7 +349,7 @@ static void gfx_sdl_handle_events(void) {
|
||||
event.window.windowID == SDL_GetWindowID(wnd)) {
|
||||
// We listen specifically for main window close because closing main window
|
||||
// on macOS does not trigger SDL_Quit.
|
||||
exit(0);
|
||||
videoRequestQuit("window closed (render pump)"); // D344
|
||||
} else if (event.window.event == SDL_WINDOWEVENT_FOCUS_LOST) {
|
||||
inputSetMouseGrab(0); // free + show cursor on alt-tab (also handled by video.c's pump; whichever dequeues it)
|
||||
} else if (event.window.event == SDL_WINDOWEVENT_FOCUS_GAINED) {
|
||||
@@ -353,7 +357,7 @@ static void gfx_sdl_handle_events(void) {
|
||||
}
|
||||
break;
|
||||
case SDL_QUIT:
|
||||
exit(0);
|
||||
videoRequestQuit("quit event (render pump)"); // D344
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -372,6 +376,20 @@ extern "C" void gfx_sdl_make_context_current(void) {
|
||||
}
|
||||
}
|
||||
|
||||
/* D344: called on the render thread when a quit was requested, at a frame
|
||||
* boundary: wait for the GPU to drain everything this process queued, then
|
||||
* unbind the context, so process teardown never lands inside the driver. */
|
||||
extern "C" void gfx_sdl_park_for_exit(void) {
|
||||
if (ctx && SDL_GL_GetCurrentContext() == ctx) {
|
||||
typedef void (SDLCALL *FinishFn)(void); // = APIENTRY on Windows
|
||||
FinishFn finish = (FinishFn)SDL_GL_GetProcAddress("glFinish");
|
||||
if (finish) {
|
||||
finish();
|
||||
}
|
||||
SDL_GL_MakeCurrent(NULL, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
/* Release the GL context from the calling (host main) thread. WGL only allows
|
||||
* a context to be current on one thread at a time, and while another thread
|
||||
* still holds it, wglMakeContextCurrent fails with "The requested resource is
|
||||
|
||||
@@ -37,6 +37,13 @@ void videoEndFrame(void);
|
||||
* loop from main(). Exits the process on QUIT/ESC/close. */
|
||||
void videoPumpEvents(void);
|
||||
|
||||
/* D344: orderly quit. Any thread may request it; the render thread parks at
|
||||
* the next frame boundary (glFinish + context released) and the host thread
|
||||
* then exits. Never call exit() directly for a normal quit: the render thread
|
||||
* could be inside the GL driver (the 0x119 bugchecks). */
|
||||
void videoRequestQuit(const char *why);
|
||||
int videoQuitRequested(void);
|
||||
|
||||
/* The game's native video mode (NTSC 640x480, PAL 640x400). fast3d scales
|
||||
* N64 screen coordinates into window pixels using this. */
|
||||
void videoUpdateNativeResolution(s32 w, s32 h);
|
||||
|
||||
+87
-6
@@ -596,6 +596,68 @@ int videoInit(void)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---- D344: orderly quit ------------------------------------------------
|
||||
* Rendering runs on the scheduler thread with the GL context bound; quit
|
||||
* events arrive on the host thread (or on fast3d's render-thread pump).
|
||||
* exit() used to run straight from whichever pump saw the event, tearing the
|
||||
* process down while the render thread could be inside the NVIDIA driver --
|
||||
* the likely trigger of the 0x119 VIDEO_SCHEDULER_INTERNAL_ERROR bugchecks
|
||||
* (findings D344). Now:
|
||||
* 1. anyone calls videoRequestQuit();
|
||||
* 2. the render thread, at its next frame boundary, glFinish()es, unbinds
|
||||
* the context and parks for good (videoRenderPark);
|
||||
* 3. the host thread waits until the render thread is parked or outside a
|
||||
* frame (max QUIT_WAIT_MS), then exit(0) (atexit saves config).
|
||||
* Frame entry sets s_inFrame BEFORE checking s_quitReq, and the host sets
|
||||
* s_quitReq BEFORE reading s_inFrame (SDL atomics are sequentially
|
||||
* consistent), so a frame can never start once the host has decided to exit. */
|
||||
#define QUIT_WAIT_MS 2000
|
||||
static SDL_atomic_t s_quitReq;
|
||||
static SDL_atomic_t s_inFrame;
|
||||
static SDL_atomic_t s_parked;
|
||||
static int s_quitFrame = -1; /* GE_QUITFRAME: harness self-quit, -1 = unread */
|
||||
|
||||
void videoRequestQuit(const char *why)
|
||||
{
|
||||
if (SDL_AtomicCAS(&s_quitReq, 0, 1)) {
|
||||
sysLogPrintf(LOG_INFO, "video: quit requested (%s)", why ? why : "?");
|
||||
}
|
||||
}
|
||||
|
||||
int videoQuitRequested(void)
|
||||
{
|
||||
return SDL_AtomicGet(&s_quitReq) != 0;
|
||||
}
|
||||
|
||||
/* Render thread only. Never returns. */
|
||||
static void videoRenderPark(void)
|
||||
{
|
||||
gfx_sdl_park_for_exit();
|
||||
SDL_AtomicSet(&s_inFrame, 0);
|
||||
SDL_AtomicSet(&s_parked, 1);
|
||||
for (;;) {
|
||||
sysSleep(100000);
|
||||
}
|
||||
}
|
||||
|
||||
/* Host thread: exit once the render thread is out of the GL driver. */
|
||||
static void videoHostExitIfRequested(void)
|
||||
{
|
||||
if (!SDL_AtomicGet(&s_quitReq)) {
|
||||
return;
|
||||
}
|
||||
Uint32 start = SDL_GetTicks();
|
||||
while (!SDL_AtomicGet(&s_parked) && SDL_AtomicGet(&s_inFrame) &&
|
||||
SDL_GetTicks() - start < QUIT_WAIT_MS) {
|
||||
SDL_Delay(2);
|
||||
}
|
||||
sysLogPrintf(LOG_INFO, "video: exiting (render %s after %u ms)",
|
||||
SDL_AtomicGet(&s_parked) ? "parked"
|
||||
: (SDL_AtomicGet(&s_inFrame) ? "STILL IN FRAME (timeout)" : "idle"),
|
||||
(unsigned)(SDL_GetTicks() - start));
|
||||
exit(0);
|
||||
}
|
||||
|
||||
void videoDestroy(void)
|
||||
{
|
||||
if (initDone) {
|
||||
@@ -609,6 +671,12 @@ void videoStartFrame(void)
|
||||
if (!initDone) {
|
||||
return;
|
||||
}
|
||||
/* D344: enter the frame first, then check for a quit (see above). */
|
||||
SDL_AtomicSet(&s_inFrame, 1);
|
||||
if (SDL_AtomicGet(&s_quitReq)) {
|
||||
videoRenderPark();
|
||||
}
|
||||
|
||||
/* Rendering runs on the game's scheduler thread; the GL context was
|
||||
* created on the host main thread. */
|
||||
gfx_sdl_make_context_current();
|
||||
@@ -647,13 +715,13 @@ void videoPumpEvents(void)
|
||||
* scheduler thread (must run here, on the window's creating thread). */
|
||||
videoDrainWindowRequests();
|
||||
inputApplyMouseRequests(); /* D287: mouse mode/cursor, same rule */
|
||||
videoHostExitIfRequested(); /* D344: quit posted last pump or by another thread */
|
||||
|
||||
SDL_Event ev;
|
||||
while (SDL_PollEvent(&ev)) {
|
||||
switch (ev.type) {
|
||||
case SDL_QUIT:
|
||||
sysLogPrintf(LOG_INFO, "video: quit requested");
|
||||
exit(0);
|
||||
videoRequestQuit("quit event");
|
||||
break;
|
||||
case SDL_KEYDOWN:
|
||||
/* D145: bare ESC used to exit(0). On the front-end / debrief
|
||||
@@ -663,8 +731,7 @@ void videoPumpEvents(void)
|
||||
* (back / cancel) via input.c; quitting is window-close (the X) or
|
||||
* Alt+F4 only. */
|
||||
if ((ev.key.keysym.sym == SDLK_F4) && (ev.key.keysym.mod & KMOD_ALT)) {
|
||||
sysLogPrintf(LOG_INFO, "video: Alt+F4 -> quit");
|
||||
exit(0);
|
||||
videoRequestQuit("Alt+F4");
|
||||
} else if (ev.key.keysym.sym == SDLK_F12 && !ev.key.repeat) {
|
||||
screenshotReq = 1;
|
||||
} else if (ev.key.keysym.sym == SDLK_F10 && !ev.key.repeat) {
|
||||
@@ -701,8 +768,7 @@ void videoPumpEvents(void)
|
||||
break;
|
||||
case SDL_WINDOWEVENT:
|
||||
if (ev.window.event == SDL_WINDOWEVENT_CLOSE) {
|
||||
sysLogPrintf(LOG_INFO, "video: window closed");
|
||||
exit(0);
|
||||
videoRequestQuit("window closed");
|
||||
} else if (ev.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) {
|
||||
gfx_sdl_update_cached_size();
|
||||
} else if (ev.window.event == SDL_WINDOWEVENT_FOCUS_LOST) {
|
||||
@@ -792,6 +858,21 @@ void videoEndFrame(void)
|
||||
}
|
||||
gfx_end_frame();
|
||||
|
||||
/* D344: the frame (and its swap) is done. Park here on a quit request, so
|
||||
* the host never exits under an in-flight frame. GE_QUITFRAME=<n> is the
|
||||
* harness's clean self-quit (replaces `timeout` kills). */
|
||||
if (s_quitFrame == -1) {
|
||||
const char *q = getenv("GE_QUITFRAME");
|
||||
s_quitFrame = (q && atoi(q) > 0) ? atoi(q) : 0;
|
||||
}
|
||||
if (s_quitFrame > 0 && frames + 1 >= (u32)s_quitFrame) {
|
||||
videoRequestQuit("GE_QUITFRAME");
|
||||
}
|
||||
if (SDL_AtomicGet(&s_quitReq)) {
|
||||
videoRenderPark();
|
||||
}
|
||||
SDL_AtomicSet(&s_inFrame, 0);
|
||||
|
||||
++frames;
|
||||
++fpsNumFrames;
|
||||
|
||||
|
||||
@@ -67,8 +67,12 @@ printf '[Window]\nWidth = 640\nHeight = 480\n' > "$INI"
|
||||
|
||||
# --- run ---------------------------------------------------------------------
|
||||
rm -rf ppm
|
||||
PATH="/c/msys64/mingw64/bin:$PATH" GE_PCDUMP="$DUMP" \
|
||||
timeout 90 ./build-pc/ge007.x86_64.exe "-level_$LEVEL" > "$LOG" 2>&1
|
||||
# D344: quit cleanly 30 frames after the last capture (GE_QUITFRAME) instead
|
||||
# of being killed by `timeout`; killing the game mid-frame is the likely
|
||||
# trigger of the host's 0x119 bugchecks. timeout stays as a backstop only.
|
||||
DUMP_HI=${DUMP#*-}; DUMP_HI=${DUMP_HI%%:*}
|
||||
PATH="/c/msys64/mingw64/bin:$PATH" GE_PCDUMP="$DUMP" GE_QUITFRAME=$((DUMP_HI + 30)) \
|
||||
timeout 120 ./build-pc/ge007.x86_64.exe "-level_$LEVEL" > "$LOG" 2>&1
|
||||
|
||||
if [ "$GOLDEN" = 1 ]; then
|
||||
python tools_pc/framediff.py ppm > "$FRAMEDIFF_OUT" 2>&1
|
||||
|
||||
Reference in new issue
Block a user