Files
psxrecomp/runtime/include/present_thread.h
Shokunin 956262a934 Present thread: never drop a guest frame on an unsignaled fence
pt_cb_present tested the composing context's readiness fence with a zero
timeout and dropped any slot whose commands had not finished, guest
frames included. Nothing was swapped, so the screen held the previous
image while emulation ran on: seconds of 1-10 fps on an 8K display while
every counter reported 60. The presenter now makes its GPU queue wait on
a guest frame's fence (glWaitSync; the CPU does not block). Only optional
temporal frames may still be dropped when unready or late.

present_loop counted a dropped image as a present; it now counts swaps
in presents and drops in a new dropped field (debug JSON
present_thread.dropped).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 01:28:42 -07:00

84 lines
3.5 KiB
C

/* present_thread.h — opt-in present thread under the render thread
* ([video] present_thread, docs/RENDER_THREAD.md "Present thread").
*
* The thread that composes frames (the render thread, or the emulation thread
* while it holds the context at a sync point) draws each presented frame into
* an offscreen slot instead of the window, then hands the slot over; the
* present thread owns a second graphics context on the same window and does
* the final copy and the swap. The swap's wait on the window compositor then
* lands on the present thread, not on rendering.
*
* This core is backend-neutral: a ring of `slots` numbered slots, each FREE,
* COMPOSING (the producer's current slot, exactly one), QUEUED or PRESENTING.
* Queued slots are presented in submission order. The producer blocks in
* pt_submit() only while every other slot is queued or presenting (the
* display is the bottleneck; that wait is the swap's wait, moved).
*
* Fences are opaque: `ready` travels with a submitted slot to present();
* present() returns a `done` token (e.g. a fence after its copy) that the
* producer receives when it next takes that slot, before drawing into it.
*
* Producer calls (pt_current / pt_submit) may come from different threads
* over time, but never concurrently (the context hand-off orders them). */
#ifndef PSX_PRESENT_THREAD_H
#define PSX_PRESENT_THREAD_H
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
#define PT_MAX_SLOTS 4
/* Present thread: make the present context current (current != 0, return 0 on
* failure: pt_start then fails) or release it (current == 0). */
typedef int (*PtCtxFn)(void *user, int current);
/* Present thread: show `slot` (wait for `ready`, copy, swap). Returns the
* slot's done token (may be NULL). */
typedef void *(*PtPresentFn)(void *user, int slot, void *ready);
/* Present thread, after the last present at stop or for a token nobody will
* collect: dispose of a done token. Optional. */
typedef void (*PtDisposeFn)(void *user, void *token);
typedef struct PtConfig {
int slots; /* 2..PT_MAX_SLOTS */
PtCtxFn ctx;
PtPresentFn present;
PtDisposeFn dispose; /* optional */
void *user;
} PtConfig;
/* Start the thread; returns once its ctx(1) has run. 1 on success (the
* producer's current slot is 0), 0 on failure (nothing running). */
int pt_start(const PtConfig *cfg);
/* Present everything queued, release the context on the present thread,
* join. Done tokens still held are passed to dispose on the present thread
* before it releases its context. Safe when not running. */
void pt_stop(void);
int pt_running(void);
/* Producer: the slot being composed. */
int pt_current(void);
/* Producer: queue the current slot with `ready`, then take the next free slot
* (blocking while none is). *next_done receives that slot's done token (NULL
* if none); *wait_ns the time spent blocked. Returns the new current slot, or
* -1 when not running. */
int pt_submit(void *ready, void **next_done, uint64_t *wait_ns);
/* Optional frame: publish only if another slot is already free. Returns -1
* without changing the current slot or taking ownership of ready otherwise. */
int pt_try_submit(void *ready, void **next_done);
/* Producer: wait until nothing is queued or presenting. */
void pt_drain(void);
typedef struct PtStats {
uint64_t submits, presents, dropped, waits, wait_ns, present_ns, present_max_ns;
int slots, queued_high;
} PtStats;
void pt_get_stats(PtStats *out);
#ifdef __cplusplus
}
#endif
#endif /* PSX_PRESENT_THREAD_H */