mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 10:19:47 -04:00
479cbb789c
One frame behind is the normal pipelined state when the render thread is the slower side; skipping on it dropped nearly every present at 4K widescreen. rt_frames_ahead() counts the complete frames queued after the one being replayed; the backend skips only at two, where the emulation thread is at the in-flight bound waiting on the render thread. The unit test checks the count never exceeds the bound and that both cases occur under a slow consumer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
124 lines
5.1 KiB
C
124 lines
5.1 KiB
C
/* render_thread.h — opt-in render thread core ([video] render_thread).
|
|
*
|
|
* The emulation thread records host draw work into a single-producer /
|
|
* single-consumer command ring; the render thread owns the graphics context
|
|
* and replays the ring in order. This header is backend-neutral: it knows
|
|
* nothing about OpenGL. The OpenGL backend (gpu_gl_renderer.c) supplies the
|
|
* command set (exec callback) and the context hand-off (ctx callback).
|
|
*
|
|
* Model (docs/RENDER_THREAD.md):
|
|
* - Records are appended with rt_cmd_begin()/rt_cmd_commit() and replayed in
|
|
* order by exec() on the render thread. A record never splits across the
|
|
* ring end; payloads are copied, so the producer may reuse its buffers.
|
|
* - rt_frame_end() closes a guest frame. At most max_frames closed frames may
|
|
* wait in the ring; past that the emulation thread blocks (backpressure).
|
|
* While replaying, rt_frames_ahead() counts the complete frames already
|
|
* recorded after the one being replayed, so the backend can skip
|
|
* presenting a frame it is far behind on.
|
|
* - rt_acquire() is the one sync point: it waits until every record has been
|
|
* executed, has the render thread release the context, and makes it
|
|
* current on the calling (emulation) thread. The emulation thread then
|
|
* holds the context ("held") until rt_release() hands it back. While held,
|
|
* the backend calls its functions directly, exactly as with no render
|
|
* thread at all.
|
|
* - No lock is held while exec() runs. Locks only guard sleeping/waking.
|
|
*
|
|
* Every function except the rt_on_render_thread() / rt_frames_ahead()
|
|
* queries is emulation-thread only. */
|
|
#ifndef PSX_RENDER_THREAD_H
|
|
#define PSX_RENDER_THREAD_H
|
|
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
/* Record header. size covers header + payload, rounded up to RT_ALIGN. */
|
|
typedef struct RtCmd {
|
|
uint32_t size;
|
|
uint16_t op; /* backend opcode; 0 is reserved (padding/frame) */
|
|
uint16_t flags;
|
|
uint32_t payload; /* payload bytes actually used */
|
|
uint32_t seq; /* low 32 bits of the record sequence number */
|
|
} RtCmd;
|
|
|
|
#define RT_ALIGN 16u
|
|
|
|
/* exec: replay one record on the render thread. ctx: make the graphics
|
|
* context current on the calling thread (current != 0) or release it
|
|
* (current == 0; the backend flushes first). */
|
|
typedef void (*RtExecFn)(void *user, const RtCmd *cmd, const void *payload);
|
|
typedef void (*RtCtxFn)(void *user, int current);
|
|
|
|
typedef struct RtConfig {
|
|
size_t ring_bytes; /* power of two; >= 1 MiB */
|
|
int max_frames; /* closed frames allowed in flight (>= 1) */
|
|
RtExecFn exec;
|
|
RtCtxFn ctx;
|
|
void *user;
|
|
} RtConfig;
|
|
|
|
/* Start: the context must be current on the calling thread; on success it is
|
|
* released here and made current on the new render thread. Returns 1 on
|
|
* success, 0 (nothing started, context still current) on failure. */
|
|
int rt_start(const RtConfig *cfg);
|
|
/* Stop: drain every record, join the thread and make the context current on
|
|
* the calling thread again. Safe when not running. */
|
|
void rt_stop(void);
|
|
int rt_running(void);
|
|
int rt_on_render_thread(void);
|
|
|
|
/* Append a record with room for payload_bytes; returns the payload pointer
|
|
* (16-byte aligned) or NULL when the record can never fit (larger than half
|
|
* the ring) — the caller then acquires and runs the work directly. Blocks
|
|
* while the ring is full. Must be followed by rt_cmd_commit() before any other
|
|
* rt_* call. op 0 is reserved. */
|
|
void *rt_cmd_begin(uint16_t op, uint16_t flags, uint32_t payload_bytes);
|
|
void rt_cmd_commit(void);
|
|
|
|
/* Close a guest frame: enqueue a frame marker, then block while more than
|
|
* max_frames closed frames are unconsumed. */
|
|
void rt_frame_end(void);
|
|
|
|
/* Render-thread query while executing: how many complete frames are queued
|
|
* after the frame whose record is being executed (0 = it is the newest). */
|
|
int rt_frames_ahead(void);
|
|
|
|
/* Sync point. Drain, then move the context to the calling thread (held).
|
|
* reason is a static string kept in the acquire ring. No-op when already held
|
|
* or not running. */
|
|
void rt_acquire(const char *reason);
|
|
/* Hand the context back to the render thread. No-op unless held. */
|
|
void rt_release(void);
|
|
int rt_held(void);
|
|
/* Wait until every committed record has executed (context stays where it
|
|
* is). Used by tests and by rt_acquire. */
|
|
void rt_drain(void);
|
|
|
|
/* Statistics (emulation thread). */
|
|
typedef struct RtStats {
|
|
uint64_t records, bytes, frames_produced, frames_consumed;
|
|
uint64_t acquires, releases, oversize;
|
|
uint64_t backpressure_waits, ring_full_waits;
|
|
uint64_t backpressure_ns, ring_full_ns, acquire_ns;
|
|
uint64_t render_busy_ns, render_idle_ns;
|
|
uint64_t ring_high_water;
|
|
int max_frames, running, held;
|
|
} RtStats;
|
|
void rt_get_stats(RtStats *out);
|
|
|
|
/* Ring of the most recent sync points (newest first). Returns the count. */
|
|
typedef struct RtAcquireEvent {
|
|
uint64_t frame; /* frames_produced at the time */
|
|
uint64_t wait_ns; /* drain + hand-off time */
|
|
const char *reason;
|
|
} RtAcquireEvent;
|
|
int rt_acquire_events(RtAcquireEvent *out, int cap);
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
#endif /* PSX_RENDER_THREAD_H */
|