Render thread: queue the headless presented-image capture

--headless-opengl captured the presented image into the ring every frame
through gl_renderer_capture_display_hires, a sync point, so a headless
run (fp_identity) held the context for most of every frame. The capture
is now recorded at the same point of the frame (gl_renderer_ring_capture)
and done by the render thread; ring readers already sync first. A rect the
GL surface cannot serve is skipped there rather than resolved through a
CPU readback, and ensure_cpu refuses to run on the render thread at all:
its readback would land in the private upload source and mark the CPU
side current without gpu.c's array seeing the pixels.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Shokunin
2026-10-06 01:33:42 -07:00
parent 2e4badfc3f
commit e336e8d3e4
3 changed files with 76 additions and 1 deletions
+2
View File
@@ -482,6 +482,8 @@ void gl_renderer_render_thread_frame_boundary(void);
void gl_renderer_render_thread_sync(const char *reason);
int gl_renderer_render_thread_json(char *out, size_t cap);
int gl_renderer_fbo_peek_deferred(int x, int y, int w, int h, uint16_t *out);
void gl_renderer_ring_capture(uint32_t frame, int wide, int base_x, int disp_y, int disp_h,
int cx, int cy, int cw, int ch);
#ifdef __cplusplus
}
+60 -1
View File
@@ -407,13 +407,14 @@ enum {
RTH_OFFSET, RTH_WIDE_CONFIGURE, RTH_WIDE_VIEW, RTH_WIDE_TARGET,
RTH_WIDE_DISABLE, RTH_WIDE_CLEAR, RTH_WIDE_CLEAR_MARGINS, RTH_PROJ_TRI,
RTH_WIDE_RECOVERY, RTH_INTERP_SUSPENDED, RTH_PRESENT_VRAM,
RTH_PRESENT_WIDE, RTH_STATE, RTH_PEEK
RTH_PRESENT_WIDE, RTH_STATE, RTH_PEEK, RTH_RING_CAPTURE
};
static int rth_record_mode(void);
static int rth_rec_ints(uint16_t op, uint16_t flags, int n, const int32_t *v);
static uint16_t rth_prim_flags(void);
static int rth_record_present(uint16_t op, int n, const int32_t *v);
static int rth_mirror_wide_present_ok(int base_x);
static void rth_ring_capture_now(const int32_t *a);
/* Emulation-side mirror: the recorded wide target is a live surface. */
static int s_rthm_cur = 0;
#define RTH_REC(op, fl, ...) do { const int32_t rth_v_[] = { __VA_ARGS__ }; \
@@ -2597,6 +2598,11 @@ static int cpu_vram_authoritative(void) {
static void ensure_cpu(void) {
extern int psx_netplay_active(void);
if (!s_raster_ok || !s_gpu_dirty) return;
/* Guest-visible readbacks run on the emulation thread under a sync point
* and land in gpu.c's array. On the render thread s_vram is the private
* upload source: reading back there would mark the CPU side current
* without the guest's array ever seeing the pixels. */
if (rth_replaying()) return;
/* Dual-raster / netplay and depth24: CPU VRAM is written on every GP0.
* Packed RGB888 movie uploads are deliberately absent from the FBO;
* reading it back would overwrite the movie with stale 1555 words. */
@@ -9604,6 +9610,9 @@ static void gl_rth_exec(void *user, const RtCmd *c, const void *payload) {
case RTH_STATE:
s_rths_flat_bd = v[0]; s_rths_vp_w = v[1]; s_rths_bg_full = v[2];
break;
#ifndef PSX_NO_DEBUG_TOOLS
case RTH_RING_CAPTURE: rth_ring_capture_now(v); break;
#endif
case RTH_PEEK: {
uint64_t ptr = (uint64_t)(uint32_t)v[4] | ((uint64_t)(uint32_t)v[5] << 32);
(void)gl_renderer_fbo_peek(v[0], v[1], v[2], v[3], (uint16_t *)(uintptr_t)ptr);
@@ -9723,4 +9732,54 @@ int gl_renderer_render_thread_json(char *out, size_t cap) {
return k;
}
#ifndef PSX_NO_DEBUG_TOOLS
/* Presented-image ring capture for --headless-opengl (main.cpp), at this point
* of the command stream: the native-wide surface slice when wide != 0, else
* the display rect at internal resolution. With the render thread recording
* it is queued (ring readers sync first); a rect the GL surface cannot serve
* is skipped there instead of resolving through a CPU readback. */
static void rth_ring_capture_now(const int32_t *a) {
static uint32_t *buf = NULL;
static size_t cap = 0;
const uint32_t frame = (uint32_t)a[0];
const int wide = a[1], base_x = a[2], disp_y = a[3], disp_h = a[4];
const int cx = a[5], cy = a[6], cw = a[7], ch = a[8];
size_t need = (size_t)1024 * 4 * 512 * 4;
if ((size_t)cw * s_out_scale * ch * s_out_scale > need)
need = (size_t)cw * s_out_scale * ch * s_out_scale;
if (cap < need) {
uint32_t *nb = (uint32_t *)realloc(buf, need * 4u);
if (!nb) return;
buf = nb; cap = need;
}
if (wide) {
int w = 0, h = 0;
if (glb_wide_dump_full(buf, (int)cap, &w, &h, base_x) > 0 && h >= 512) {
const int sc = h / 512, y0 = disp_y * sc, rows = disp_h * sc;
if (y0 + rows <= h) {
present_image_ring_push_argb(frame, buf + (size_t)y0 * w, w, rows, w);
return;
}
}
}
const int w = cw * s_out_scale, h = ch * s_out_scale;
int n;
if (rth_replaying())
n = (s_raster_ok && !s_depth24_skip_up)
? gl_read_display_argb(cx, cy, cw, ch, buf, w * 4, INT_MAX, NULL, NULL) : 0;
else
n = gl_renderer_capture_display_hires(buf, w * 4, cx, cy, cw, ch);
if (n == w * h) present_image_ring_push_argb(frame, buf, w, h, w);
}
void gl_renderer_ring_capture(uint32_t frame, int wide, int base_x, int disp_y, int disp_h,
int cx, int cy, int cw, int ch) {
const int32_t a[9] = { (int32_t)frame, wide, base_x, disp_y, disp_h, cx, cy, cw, ch };
if (rth_record_mode()) { rth_rec_ints(RTH_RING_CAPTURE, 0, 9, a); return; }
GL_RT_SYNC("ring_capture");
rth_ring_capture_now(a);
}
#else
static void rth_ring_capture_now(const int32_t *a) { (void)a; }
#endif
const GpuRenderBackend *gl_backend_get(void) { return s_rth_on ? &GL_RT_BACKEND : &GL_BACKEND; }
+14
View File
@@ -7837,6 +7837,20 @@ static void headless_present_image_ring_capture(void) {
gpu_get_display_info(&di);
if (di.disabled || di.width == 0 || di.height == 0 || di.depth24) return;
const bool fmv_frame = !g_ws_engaged || gpu_ws_present_native_43() != 0;
#ifndef PSX_SDL_NO_RENDER
/* Render thread: the same capture, queued at this point of the frame
* instead of a per-frame sync point (gl_renderer_ring_capture). */
if (g_headless_opengl && gl_renderer_render_thread_active()) {
int slot = netplay_local_viewport_slot();
int cw = slot >= 0 ? (int)di.width / 2 : (int)di.width;
int cx = (int)di.display_x + (slot == 1 ? (int)di.width - cw : 0);
gl_renderer_ring_capture((uint32_t)s_frame_count,
(!fmv_frame && ws_native_wide_active() && gr_wide_supported()) ? 1 : 0,
(int)di.display_x, (int)di.display_y, (int)di.height,
cx, (int)di.display_y, cw, (int)di.height);
return;
}
#endif
static std::vector<uint32_t> buf;
int w = 0, h = 0;
uint32_t vx = 0, vy = 0, vw = di.width, vh = di.height;