diff --git a/port/fast3d/gfx_opengl.cpp b/port/fast3d/gfx_opengl.cpp index 66e5aea1..532a2a69 100644 --- a/port/fast3d/gfx_opengl.cpp +++ b/port/fast3d/gfx_opengl.cpp @@ -1083,7 +1083,15 @@ extern "C" bool gfx_opengl_dump_bound_fbo(uint32_t width, uint32_t height, const size_t n = (size_t)width * height * 3; uint8_t* pixels = (uint8_t*)malloc(n); if (!pixels) return false; + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + glReadBuffer(GL_BACK); + glPixelStorei(GL_PACK_ALIGNMENT, 1); + glFinish(); glReadPixels(0, 0, (GLsizei)width, (GLsizei)height, GL_RGB, GL_UNSIGNED_BYTE, pixels); + { + GLenum e = glGetError(); + if (e) sysLogPrintf(LOG_WARNING, "dump_bound_fbo: glReadPixels err 0x%x (%ux%u)", (unsigned)e, width, height); + } FILE* f = fopen(path, "wb"); if (!f) { free(pixels); return false; } fprintf(f, "P6\n%u %u\n255\n", width, height); @@ -1199,7 +1207,11 @@ bool gfx_opengl_start_draw_to_framebuffer(int fb_id, float noise_scale) { if (noise_scale != 0.0f) { current_noise_scale = 1.0f / noise_scale; } - glBindFramebuffer(GL_FRAMEBUFFER, fb.fbo); + /* fb_id 0 is the on-screen target: bind the window's default + * framebuffer (GL name 0), not framebuffers[0].fbo (a generated but + * attachment-less FBO -> incomplete -> nothing renders on a strict + * GL implementation like Mesa/llvmpipe). */ + glBindFramebuffer(GL_FRAMEBUFFER, fb_id == 0 ? 0 : fb.fbo); current_framebuffer = fb_id; return true; } else { diff --git a/port/fast3d/gfx_sdl2.cpp b/port/fast3d/gfx_sdl2.cpp index 8bdac3e5..ce1a7650 100644 --- a/port/fast3d/gfx_sdl2.cpp +++ b/port/fast3d/gfx_sdl2.cpp @@ -427,10 +427,19 @@ static inline void sync_framerate_with_timer(void) { previous_time = t; } +/* Optional hook run with the freshly-composited frame still in the back + * buffer, immediately before the swap. video.c uses it for GE_PCDUMP / + * screenshots: reading the back buffer AFTER SDL_GL_SwapWindow is undefined + * on buffer-exchange drivers (Mesa, WSLg) and yields a black capture. */ +extern "C" void (*gfx_pre_swap_hook)(void) = NULL; + static void gfx_sdl_swap_buffers_begin(void) { if (target_fps) { sync_framerate_with_timer(); } + if (gfx_pre_swap_hook) { + gfx_pre_swap_hook(); + } SDL_GL_SwapWindow(wnd); } diff --git a/port/src/video.c b/port/src/video.c index 4bb774ba..9e87305f 100644 --- a/port/src/video.c +++ b/port/src/video.c @@ -100,6 +100,10 @@ static u32 frames = 0; /* Set by the host event pump (F12), consumed on the render thread in * videoEndFrame where a GL context is current. */ static volatile int screenshotReq = 0; + +/* Pre-swap capture hook (defined below, registered in videoInit). */ +static void videoPreSwapCapture(void); +extern void (*gfx_pre_swap_hook)(void); static double fpsWindowStart = 0.0; static int fpsNumFrames = 0; static float vidAvgFPS = 0.f; @@ -172,6 +176,8 @@ int videoInit(void) * still needs a current context on this thread. */ gfx_sdl_release_context(); + gfx_pre_swap_hook = videoPreSwapCapture; + initDone = 1; sysLogPrintf(LOG_INFO, "video: %dx%d window (native %dx%d)", (int)gfx_current_dimensions.width, (int)gfx_current_dimensions.height, @@ -289,18 +295,13 @@ void videoSubmitCommands(Gfx *cmds) gfx_run(cmds); } -void videoEndFrame(void) +/* Runs from gfx_sdl_swap_buffers_begin with the composited frame still in the + * back buffer, just before SDL_GL_SwapWindow. Reading the back buffer after + * the swap is undefined on buffer-exchange drivers (Mesa/WSLg) -> black. */ +static void videoPreSwapCapture(void) { - if (!initDone) { - return; - } - gfx_end_frame(); - - /* TEMP D70 (env-gated, debug-only, env-gated): GE_PCDUMP="first-last" - * or "first-last:step" dumps the presented frame as ./ppm/frame_NNNNNN.ppm - * (one frame behind real time — read happens after SwapWindow). Used to - * pixel-verify the intro's 3D content. Also honours [Debug] FrameDump in - * ge007.ini (env var wins). */ + /* GE_PCDUMP="first-last" / "first-last:step" -> ./ppm/frame_NNNNNN.ppm. + * Also honours [Debug] FrameDump in ge007.ini (env var wins). */ const char *pcdump = configGetFrameDump(); if (pcdump) { static int lo = -1, hi = 0, step = 1; @@ -335,6 +336,14 @@ void videoEndFrame(void) sysLogPrintf(LOG_WARNING, "video: screenshot failed"); } } +} + +void videoEndFrame(void) +{ + if (!initDone) { + return; + } + gfx_end_frame(); ++frames; ++fpsNumFrames;