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
|
/* 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). */
|
* the game's scheduler thread can bind it per frame (see gfx_sdl2.cpp). */
|
||||||
void gfx_sdl_release_context(void);
|
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
|
/* Refresh the cached drawable size after a window resize (called by the
|
||||||
* host thread's event pump). */
|
* host thread's event pump). */
|
||||||
|
|||||||
@@ -9,6 +9,8 @@
|
|||||||
#include "gfx_screen_config.h"
|
#include "gfx_screen_config.h"
|
||||||
#include "input.h"
|
#include "input.h"
|
||||||
|
|
||||||
|
extern "C" void videoRequestQuit(const char *why); // port/src/video.c (D344)
|
||||||
|
|
||||||
static SDL_Window* wnd;
|
static SDL_Window* wnd;
|
||||||
static SDL_GLContext ctx;
|
static SDL_GLContext ctx;
|
||||||
static SDL_Renderer* renderer;
|
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)) {
|
} 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
|
// D145: Alt+F4 quits; bare ESC no longer does (it is the
|
||||||
// menu "back" key -- see port/src/video.c / input.c).
|
// 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) {
|
} else if (event.key.keysym.sym == SDLK_ESCAPE && !event.key.repeat) {
|
||||||
// WI-1: this render-thread pump and port/src/video.c's
|
// WI-1: this render-thread pump and port/src/video.c's
|
||||||
// host-thread pump both drain the same SDL queue, so either
|
// 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)) {
|
event.window.windowID == SDL_GetWindowID(wnd)) {
|
||||||
// We listen specifically for main window close because closing main window
|
// We listen specifically for main window close because closing main window
|
||||||
// on macOS does not trigger SDL_Quit.
|
// on macOS does not trigger SDL_Quit.
|
||||||
exit(0);
|
videoRequestQuit("window closed (render pump)"); // D344
|
||||||
} else if (event.window.event == SDL_WINDOWEVENT_FOCUS_LOST) {
|
} 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)
|
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) {
|
} else if (event.window.event == SDL_WINDOWEVENT_FOCUS_GAINED) {
|
||||||
@@ -353,7 +357,7 @@ static void gfx_sdl_handle_events(void) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case SDL_QUIT:
|
case SDL_QUIT:
|
||||||
exit(0);
|
videoRequestQuit("quit event (render pump)"); // D344
|
||||||
break;
|
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
|
/* 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
|
* 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
|
* 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. */
|
* loop from main(). Exits the process on QUIT/ESC/close. */
|
||||||
void videoPumpEvents(void);
|
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
|
/* The game's native video mode (NTSC 640x480, PAL 640x400). fast3d scales
|
||||||
* N64 screen coordinates into window pixels using this. */
|
* N64 screen coordinates into window pixels using this. */
|
||||||
void videoUpdateNativeResolution(s32 w, s32 h);
|
void videoUpdateNativeResolution(s32 w, s32 h);
|
||||||
|
|||||||
+87
-6
@@ -596,6 +596,68 @@ int videoInit(void)
|
|||||||
return 0;
|
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)
|
void videoDestroy(void)
|
||||||
{
|
{
|
||||||
if (initDone) {
|
if (initDone) {
|
||||||
@@ -609,6 +671,12 @@ void videoStartFrame(void)
|
|||||||
if (!initDone) {
|
if (!initDone) {
|
||||||
return;
|
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
|
/* Rendering runs on the game's scheduler thread; the GL context was
|
||||||
* created on the host main thread. */
|
* created on the host main thread. */
|
||||||
gfx_sdl_make_context_current();
|
gfx_sdl_make_context_current();
|
||||||
@@ -647,13 +715,13 @@ void videoPumpEvents(void)
|
|||||||
* scheduler thread (must run here, on the window's creating thread). */
|
* scheduler thread (must run here, on the window's creating thread). */
|
||||||
videoDrainWindowRequests();
|
videoDrainWindowRequests();
|
||||||
inputApplyMouseRequests(); /* D287: mouse mode/cursor, same rule */
|
inputApplyMouseRequests(); /* D287: mouse mode/cursor, same rule */
|
||||||
|
videoHostExitIfRequested(); /* D344: quit posted last pump or by another thread */
|
||||||
|
|
||||||
SDL_Event ev;
|
SDL_Event ev;
|
||||||
while (SDL_PollEvent(&ev)) {
|
while (SDL_PollEvent(&ev)) {
|
||||||
switch (ev.type) {
|
switch (ev.type) {
|
||||||
case SDL_QUIT:
|
case SDL_QUIT:
|
||||||
sysLogPrintf(LOG_INFO, "video: quit requested");
|
videoRequestQuit("quit event");
|
||||||
exit(0);
|
|
||||||
break;
|
break;
|
||||||
case SDL_KEYDOWN:
|
case SDL_KEYDOWN:
|
||||||
/* D145: bare ESC used to exit(0). On the front-end / debrief
|
/* 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
|
* (back / cancel) via input.c; quitting is window-close (the X) or
|
||||||
* Alt+F4 only. */
|
* Alt+F4 only. */
|
||||||
if ((ev.key.keysym.sym == SDLK_F4) && (ev.key.keysym.mod & KMOD_ALT)) {
|
if ((ev.key.keysym.sym == SDLK_F4) && (ev.key.keysym.mod & KMOD_ALT)) {
|
||||||
sysLogPrintf(LOG_INFO, "video: Alt+F4 -> quit");
|
videoRequestQuit("Alt+F4");
|
||||||
exit(0);
|
|
||||||
} else if (ev.key.keysym.sym == SDLK_F12 && !ev.key.repeat) {
|
} else if (ev.key.keysym.sym == SDLK_F12 && !ev.key.repeat) {
|
||||||
screenshotReq = 1;
|
screenshotReq = 1;
|
||||||
} else if (ev.key.keysym.sym == SDLK_F10 && !ev.key.repeat) {
|
} else if (ev.key.keysym.sym == SDLK_F10 && !ev.key.repeat) {
|
||||||
@@ -701,8 +768,7 @@ void videoPumpEvents(void)
|
|||||||
break;
|
break;
|
||||||
case SDL_WINDOWEVENT:
|
case SDL_WINDOWEVENT:
|
||||||
if (ev.window.event == SDL_WINDOWEVENT_CLOSE) {
|
if (ev.window.event == SDL_WINDOWEVENT_CLOSE) {
|
||||||
sysLogPrintf(LOG_INFO, "video: window closed");
|
videoRequestQuit("window closed");
|
||||||
exit(0);
|
|
||||||
} else if (ev.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) {
|
} else if (ev.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) {
|
||||||
gfx_sdl_update_cached_size();
|
gfx_sdl_update_cached_size();
|
||||||
} else if (ev.window.event == SDL_WINDOWEVENT_FOCUS_LOST) {
|
} else if (ev.window.event == SDL_WINDOWEVENT_FOCUS_LOST) {
|
||||||
@@ -792,6 +858,21 @@ void videoEndFrame(void)
|
|||||||
}
|
}
|
||||||
gfx_end_frame();
|
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;
|
++frames;
|
||||||
++fpsNumFrames;
|
++fpsNumFrames;
|
||||||
|
|
||||||
|
|||||||
@@ -67,8 +67,12 @@ printf '[Window]\nWidth = 640\nHeight = 480\n' > "$INI"
|
|||||||
|
|
||||||
# --- run ---------------------------------------------------------------------
|
# --- run ---------------------------------------------------------------------
|
||||||
rm -rf ppm
|
rm -rf ppm
|
||||||
PATH="/c/msys64/mingw64/bin:$PATH" GE_PCDUMP="$DUMP" \
|
# D344: quit cleanly 30 frames after the last capture (GE_QUITFRAME) instead
|
||||||
timeout 90 ./build-pc/ge007.x86_64.exe "-level_$LEVEL" > "$LOG" 2>&1
|
# 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
|
if [ "$GOLDEN" = 1 ]; then
|
||||||
python tools_pc/framediff.py ppm > "$FRAMEDIFF_OUT" 2>&1
|
python tools_pc/framediff.py ppm > "$FRAMEDIFF_OUT" 2>&1
|
||||||
|
|||||||
Reference in new issue
Block a user